/// <summary> /// Registers the given operation descriptor. /// </summary> /// <param name="descriptor">The descriptor to register, must be non-null.</param> public void RegisterOperation(ApiOperationDescriptor descriptor) { Guard.NotNull(nameof(descriptor), descriptor); this._allOperations[descriptor.OperationType] = descriptor; foreach (var link in descriptor.Links) { this.RegisterLink(link); } }
private static string NormaliseTypeName(ApiOperationDescriptor operation) { // Replace + with _ to enable nested operation classes to compile successfully // Replace , with _ to separate generic arguments // Replace < with Of at start of generic arguments (i.e. IWrapperOfT for IWrapper<T>) // Replace > with "" at end of generic argument list return(ReflectionUtilities .PrettyTypeName(operation.OperationType) .Replace("+", "_") .Replace(",", "_") .Replace("<", "Of") .Replace(">", string.Empty) + "ExecutorPipeline"); }
internal MiddlewareBuilderContext( GeneratedMethod executeMethod, ApiOperationDescriptor descriptor, ApiDataModel model, IServiceProvider serviceProvider, InstanceFrameProvider instanceFrameProvider, bool isNested) { this.ExecuteMethod = executeMethod; this.Descriptor = descriptor; this.Model = model; this.ServiceProvider = serviceProvider; this.IsNested = isNested; this._instanceFrameProvider = instanceFrameProvider; }
/// <summary> /// Constructs a link that applies at a 'system' level, can be overriden by setting /// <see cref="ResourceType"/>. /// </summary> /// <param name="operationDescriptor">The operation this link represents.</param> /// <param name="urlFormat">The URL from which this link is accessed, may be templated.</param> /// <param name="rel">The <strong>rel</strong>ationship this link represents.</param> public ApiOperationLink(ApiOperationDescriptor operationDescriptor, string urlFormat, string rel) { Guard.NotNull(nameof(operationDescriptor), operationDescriptor); Guard.NotNull(nameof(urlFormat), urlFormat); Guard.NotNull(nameof(rel), rel); this.OperationDescriptor = operationDescriptor; this.UrlFormat = urlFormat.TrimStart('/'); this.Rel = rel; var placeholders = new List <ApiOperationLinkPlaceholder>(5); this.RoutingUrl = _queryStringRegex.Replace( _parameterRegex.Replace( this.UrlFormat, match => { var propertyName = match.Groups["propName"].Value; var property = operationDescriptor.OperationType.GetProperty( propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (property == null) { throw new InvalidOperationException( $"Property {propertyName} does not exist on operation type {operationDescriptor.OperationType.Name}."); } placeholders.Add(new ApiOperationLinkPlaceholder( match.Value, match.Index, property, match.Groups["alternatePropName"].Success ? match.Groups["alternatePropName"].Value : null, match.Groups["format"].Success ? match.Groups["format"].Value : null)); return("{" + property.Name + "}"); }), string.Empty); this.Placeholders = placeholders; // Let's precalculate this as ApiOperationLinks are expected to stay around for lifetime of the application, so may as well // pay this cost upfront. this._description = $"{this.ResourceType?.Name ?? "Root"}#{this.Rel} => {this.OperationDescriptor.OperationType.Name}"; }
/// <summary> /// Initialises a new instance of the <see cref="ApiOperationContext" /> class, using /// <see cref="ApiOperationDescriptor.CreateInstance" /> to construct the operation /// instance. /// </summary> /// <param name="serviceProvider">The service provider (typically a nested scope) for this context.</param> /// <param name="dataModel">The data model that represents the API in which this context is being executed.</param> /// <param name="operationDescriptor">A descriptor for the operation that is being executed.</param> /// <param name="token">A cancellation token to indicate the operation should stop.</param> public ApiOperationContext( IServiceProvider serviceProvider, ApiDataModel dataModel, ApiOperationDescriptor operationDescriptor, CancellationToken token) { Guard.NotNull(nameof(serviceProvider), serviceProvider); Guard.NotNull(nameof(dataModel), dataModel); Guard.NotNull(nameof(operationDescriptor), operationDescriptor); this.Descriptor = operationDescriptor; this.OperationCancelled = token; this.DataModel = dataModel; this.ServiceProvider = serviceProvider; this.Operation = operationDescriptor.CreateInstance(); this.ApmSpan = NullApmSpan.Instance; this.Data = new Dictionary <string, object>(); }
/// <summary> /// Constructs a link that applies at a resource level. /// </summary> /// <param name="operationDescriptor">The operation this link represents.</param> /// <param name="urlFormat">The URL from which this link is accessed, may be templated.</param> /// <param name="rel">The <strong>rel</strong>ationship this link represents.</param> /// <param name="resourceType">he resource from which this link can be created, for example a `UserResource` value that /// is returned from an operation. This <b>MUST</b> be an <see cref="ILinkableResource" />.</param> public ApiOperationLink(ApiOperationDescriptor operationDescriptor, string urlFormat, string rel, [CanBeNull] Type resourceType) : this(operationDescriptor, urlFormat, rel) { this.ResourceType = resourceType; if (resourceType != null) { if (resourceType.IsAssignableTo(typeof(ILinkableResource)) == false) { throw new OperationLinkFormatException( $"Resource type {resourceType.Name} is not assignable to {nameof(ILinkableResource)}, cannot add a link for {operationDescriptor.Name}"); } foreach (var placeholder in this.Placeholders) { var prop = resourceType.GetProperty(placeholder.Property.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (prop != null) { continue; } if (placeholder.AlternatePropertyName == null) { throw new OperationLinkFormatException( $"Link {urlFormat} for operation {operationDescriptor.Name} specifies placeholder {placeholder.Property.Name} that cannot be found on resource {resourceType.Name}"); } var alternateProperty = resourceType.GetProperty( placeholder.AlternatePropertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (alternateProperty == null) { throw new OperationLinkFormatException( $"Link {urlFormat} for operation {operationDescriptor.Name} specifies placeholder {placeholder.OriginalText}. Cannot find alternate property {placeholder.AlternatePropertyName} on resource {resourceType.Name}"); } } } }
public Task <ExecutionAllowed> CanShowLinkAsync(ApiOperationContext operationContext, ApiOperationDescriptor descriptor, object resource) { if (resource is TResource r && !this.IsLinkAvailableForOperation(operationContext, r)) { return(_stateCheckFailed); } return(ExecutionAllowed.YesTask); }
/// <summary> /// Always returns <see cref="ExecutionAllowed.YesTask" /> as state-based checks do not apply to the execution of /// the operations, only link generation. /// </summary> /// <param name="operationContext">The operation context.</param> /// <param name="descriptor">The operation description.</param> /// <param name="operation">The operation.</param> /// <returns><see cref="ExecutionAllowed.YesTask" />.</returns> public Task <ExecutionAllowed> CanExecuteOperationAsync(ApiOperationContext operationContext, ApiOperationDescriptor descriptor, object operation) { return(ExecutionAllowed.YesTask); }
public bool AppliesTo(ApiOperationDescriptor descriptor) { return(descriptor.OperationType == typeof(TOperation)); }
private void Generate( BlueprintApiOptions options, IServiceProvider serviceProvider, GeneratedMethod executeMethod, ApiOperationDescriptor operation, ApiDataModel model, IServiceScope serviceScope, bool isNested) { var operationContextVariable = executeMethod.Arguments[0]; var instanceFrameProvider = serviceProvider.GetRequiredService <InstanceFrameProvider>(); var dependencyInjectionVariableSource = new DependencyInjectionVariableSource(executeMethod, instanceFrameProvider); var castFrame = new ConcreteOperationCastFrame(operationContextVariable, operation.OperationType); var apiOperationContextSource = new ApiOperationContextVariableSource(operationContextVariable, castFrame.CastOperationVariable); var context = new MiddlewareBuilderContext( executeMethod, operation, model, serviceScope.ServiceProvider, instanceFrameProvider, isNested); // For the base Exception type we will add, as the first step, logging to the exception sinks. This frame DOES NOT // include a return frame, as we add that after all the other middleware builders have had chance to potentially add // more frames to perform other operations on unknown Exception context.RegisterUnhandledExceptionHandler(typeof(Exception), e => new Frame[] { // Exceptions do not escape from a pipeline because we always convert to a result type new PushExceptionToActivityFrame(e, false), }); executeMethod.Sources.Add(apiOperationContextSource); executeMethod.Sources.Add(dependencyInjectionVariableSource); foreach (var source in options.GenerationRules.VariableSources) { executeMethod.Sources.Add(source); } var startActivityFrame = ActivityFrame.Start(ActivityKind.Internal, operation.Name + (isNested ? "NestedPipeline" : "Pipeline")); executeMethod.Frames.Add(startActivityFrame); executeMethod.Frames.Add(castFrame); executeMethod.Frames.Add(new ErrorHandlerFrame(context)); executeMethod.Frames.Add(new BlankLineFrame()); foreach (var behaviour in this._builders) { if (isNested && !behaviour.SupportsNestedExecution) { continue; } if (behaviour.Matches(operation)) { executeMethod.Frames.Add(new CommentFrame(behaviour.GetType().Name)); behaviour.Build(context); executeMethod.Frames.Add(new BlankLineFrame()); } } // For the base Exception type we will add, as a last frame, a return of an OperationResult. context.RegisterUnhandledExceptionHandler(typeof(Exception), e => new[] { new ReturnFrame(new Variable(typeof(UnhandledExceptionOperationResult), $"new {typeof(UnhandledExceptionOperationResult).FullNameInCode()}({e})")), }); }
/// <inheritdoc /> public abstract bool Matches(ApiOperationDescriptor operation);