/// <summary> /// Processes the given <see cref="IGraphFieldRequest" /> against this instance /// performing the operation as defined by this entity and generating a response. /// </summary> /// <param name="resolutionContext">The resolution context containing the request and the /// runtime information needed to resolve it.</param> /// <param name="cancelToken">The cancel token monitoring the execution of a graph request.</param> /// <returns>Task<IGraphPipelineResponse>.</returns> public Task Resolve(FieldResolutionContext resolutionContext, CancellationToken cancelToken = default) { var sourceData = resolutionContext.Arguments.SourceData as IntrospectedType; if (sourceData == null) { resolutionContext.Result = Enumerable.Empty <IntrospectedEnumValue>(); } else { var includedDeprecated = resolutionContext.Arguments.ContainsKey(Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME) && (bool)resolutionContext.Arguments[Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME].Value; if (includedDeprecated) { resolutionContext.Result = sourceData.EnumValues; } else { resolutionContext.Result = sourceData.EnumValues?.Where(x => !x.IsDeprecated).ToList(); } } return(Task.CompletedTask); }
/// <summary> /// Initializes a new instance of the <see cref="FieldResolutionStartedLogEntry" /> class. /// </summary> /// <param name="context">The field context containing the necessary data to resolve /// the field and produce a reslt.</param> public FieldResolutionStartedLogEntry(FieldResolutionContext context) : base(LogEventIds.FieldResolutionStarted) { this.PipelineRequestId = context.Request.Id; this.FieldExecutionMode = context.Request.Field.Mode.ToString(); this.FieldPath = context.Request.Field.Route.Path; }
/// <summary> /// Invoke the action item as an asyncronous operation. /// </summary> /// <param name="context">The context.</param> /// <param name="next">The next.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken = default) { // create a set of validation contexts for every incoming source graph item // to capture and validate every item regardless of it being successfully resolved or failed var validationContexts = context.Request.DataSource.Items.Select( x => new FieldValidationContext(_schema, x, context.Messages)); // begin profiling of this single field of data context.Metrics?.BeginFieldResolution(context); bool fieldShouldBeCanceled = false; if (context.IsValid) { // build a collection of invokable parameters from the supplied context var executionArguments = context .InvocationContext .Arguments .Merge(context.VariableData) .WithSourceData(context.Request.DataSource.Value); // resolve the field var resolutionContext = new FieldResolutionContext(context, context.Request, executionArguments); context.Logger?.FieldResolutionStarted(resolutionContext); var task = context.Field?.Resolver?.Resolve(resolutionContext, cancelToken); await task.ConfigureAwait(false); context.Messages.AddRange(resolutionContext.Messages); this.AssignResults(context, resolutionContext); fieldShouldBeCanceled = resolutionContext.IsCancelled; context.Logger?.FieldResolutionCompleted(resolutionContext); } if (fieldShouldBeCanceled) { context.Cancel(); context.Request.DataSource.Items.ForEach(x => x.Cancel()); } // validate the resolution of the field in whatever manner that means for its current state var completionProcessor = new FieldCompletionRuleProcessor(); completionProcessor.Execute(validationContexts); // end profiling of this single field of data context.Metrics?.EndFieldResolution(context); await next(context, cancelToken).ConfigureAwait(false); // validate the final result after all downstream middleware execute // in the standard pipeline this generally means all child fields have resolved var validationProcessor = new FieldValidationRuleProcessor(); validationProcessor.Execute(validationContexts); }
/// <summary> /// Initializes a new instance of the <see cref="FieldResolutionCompletedLogEntry" /> class. /// </summary> /// <param name="context">The context.</param> public FieldResolutionCompletedLogEntry(FieldResolutionContext context) : base(LogEventIds.FieldResolutionCompleted) { this.PipelineRequestId = context.Request.Id; this.FieldPath = context.Request.InvocationContext.Field.Route.Path; this.TypeExpression = context.Request.InvocationContext.Field.TypeExpression.ToString(); this.HasData = context.Result != null; this.ResultIsValid = context.Messages.IsSucessful; }
/// <inheritdoc /> public virtual void FieldResolutionCompleted(FieldResolutionContext context) { if (!this.IsEnabled(LogLevel.Trace)) { return; } var entry = new FieldResolutionCompletedLogEntry(context); this.LogEvent(LogLevel.Trace, entry); }
/// <summary> /// Assigns the results of resolving the field to the items on the execution context. /// </summary> /// <param name="executionContext">The execution context.</param> /// <param name="resolutionContext">The resolution context.</param> private void AssignResults(GraphFieldExecutionContext executionContext, FieldResolutionContext resolutionContext) { // transfer the result to the execution context // then deteremine what (if any) data items can be updated from its value executionContext.Result = resolutionContext.Result; if (executionContext.Field.Mode == FieldResolutionMode.PerSourceItem) { if (executionContext.Request.DataSource.Items.Count == 1) { var item = executionContext.Request.DataSource.Items[0]; executionContext.ResolvedSourceItems.Add(item); item.AssignResult(resolutionContext.Result); return; } throw new GraphExecutionException( $"When attempting to resolve the field '{executionContext.Field.Route.Path}' an unexpected error occured and the request was teriminated.", executionContext.Request.Origin, new InvalidOperationException( $"The field '{executionContext.Field.Route.Parent}' has a resolution mode of '{nameof(FieldResolutionMode.PerSourceItem)}' " + $"but the execution context contains {executionContext.Request.DataSource.Items.Count} source items. The runtime is unable to determine which " + "item to assign the resultant value to.")); } else if (executionContext.Field.Mode == FieldResolutionMode.Batch) { var batchProcessor = new BatchResultProcessor( executionContext.Field, executionContext.Request.DataSource.Items, executionContext.Request.Origin); var itemsWithAssignedData = batchProcessor.Resolve(executionContext.Result); executionContext.ResolvedSourceItems.AddRange(itemsWithAssignedData); executionContext.Messages.AddRange(batchProcessor.Messages); return; } throw new ArgumentOutOfRangeException( nameof(executionContext.Field.Mode), $"The execution mode for field '{executionContext.Field.Route.Path}' cannot be resolved " + $"by {nameof(InvokeFieldResolverMiddleware<TSchema>)}. (Mode: {executionContext.Field.Mode.ToString()})"); }
/// <summary> /// Processes the given <see cref="IGraphFieldRequest" /> against this instance /// performing the operation as defined by this entity and generating a response. /// </summary> /// <param name="resolutionContext">The resolution context containing the request and the /// runtime information needed to resolve it.</param> /// <param name="cancelToken">The cancel token monitoring the execution of a graph request.</param> /// <returns>Task<IGraphPipelineResponse>.</returns> public Task Resolve(FieldResolutionContext resolutionContext, CancellationToken cancelToken = default) { if (!resolutionContext.Arguments.TryGetArgument <string>("name", out var name)) { resolutionContext.Messages.Critical("Required Argument 'name' not found."); } else { var type = _schema.FindIntrospectedType(name); if (type == null || !type.Publish) { resolutionContext.Messages.Info($"Unknown graph type. The type '{name}' does not exist on the '{_schema.Name}' schema"); } else { resolutionContext.Result = type; } } return(Task.CompletedTask); }
public async Task Resolve(FieldResolutionContext context, CancellationToken cancelToken = default) { IGraphActionResult result; try { // create a scoped controller instance for this invocation var controller = context .ServiceProvider? .GetService(_actionMethod.Parent.ObjectType) as GraphController; if (controller == null) { result = new RouteNotFoundGraphActionResult( $"The controller assigned to process the field '{context.Request.InvocationContext.Field.Route.Path}' " + "was not found."); } else { // invoke the right action method and set a result. var task = controller.InvokeActionAsync(_actionMethod, context); var returnedItem = await task.ConfigureAwait(false); result = this.EnsureGraphActionResult(returnedItem); } } catch (Exception ex) { // :( result = new InternalServerErrorGraphActionResult("Operation failed.", ex); } // resolve the final graph action output using the provided field context // in what ever manner is appropriate for the result itself await result.Complete(context).ConfigureAwait(false); }
/// <summary> /// Processes the given <see cref="IGraphFieldRequest" /> against this instance /// performing the operation as defined by this entity and generating a response. /// </summary> /// <param name="context">The context.</param> /// <param name="cancelToken">The cancel token monitoring the execution of a graph request.</param> /// <returns>Task<IGraphPipelineResponse>.</returns> public virtual async Task Resolve(FieldResolutionContext context, CancellationToken cancelToken = default) { var sourceData = context.Arguments?.SourceData; if (sourceData == null) { context.Messages.Critical( "No source data was provided to the field resolver " + $"for '{_graphMethod.Route.Path}'. Unable to complete the request.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin); return; } var typeCheck = _methodInfo.ReflectedType ?? _methodInfo.DeclaringType; if (context.Arguments.SourceData.GetType() != typeCheck) { context.Messages.Critical( "The source data provided to the field resolver " + $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( $"The method '{_graphMethod.InternalFullName}' expected source data of type " + $"'{_graphMethod.Parent.ObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' " + "which is not compatible.")); return; } try { object data = null; var paramSet = context.Arguments.PrepareArguments(_graphMethod); var invoker = InstanceFactory.CreateInstanceMethodInvoker(_graphMethod.Method); var invokeReturn = invoker(context.Arguments.SourceData, paramSet); if (_graphMethod.IsAsyncField) { if (invokeReturn is Task task) { await task.ConfigureAwait(false); data = task.ResultOrDefault(); } } else { data = invokeReturn; } context.Result = data; } catch (GraphExecutionException gee) { context.Messages.Critical( gee.Message, Constants.ErrorCodes.EXECUTION_ERROR, context.Request.Origin); } catch (Exception ex) { context.Messages.Critical( $"An unknown error occured atttempting to resolve the field '{_graphMethod.Route.Path}'. " + $"See exception for details.", Constants.ErrorCodes.UNHANDLED_EXCEPTION, context.Request.Origin, ex); } }
/// <summary> /// Processes the given <see cref="IGraphFieldRequest" /> against this instance /// performing the operation as defined by this entity and generating a response. /// </summary> /// <param name="context">The field context containing the necessary data to resolve /// the field and produce a reslt.</param> /// <param name="cancelToken">The cancel token monitoring the execution of a graph request.</param> /// <returns>Task<IGraphPipelineResponse>.</returns> public async Task Resolve(FieldResolutionContext context, CancellationToken cancelToken = default) { var data = await _func(context?.Arguments.SourceData as TSource).ConfigureAwait(false); context.Result = data; }
/// <summary> /// Processes the given <see cref="IGraphFieldRequest" /> against this instance /// performing the operation as defined by this entity and generating a response. /// </summary> /// <param name="context">The field context containing the necessary data to resolve /// the field and produce a reslt.</param> /// <param name="cancelToken">The cancel token monitoring the execution of a graph request.</param> /// <returns>Task<IGraphPipelineResponse>.</returns> public async Task Resolve(FieldResolutionContext context, CancellationToken cancelToken = default) { var sourceData = context.Arguments.SourceData; if (sourceData == null) { context.Messages.Critical( "No source data was provided to the field resolver " + $"for '{_graphMethod.Name}'. Unable to complete the request.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin); return; } // valdidate the incoming source data to ensure its process-able by this property // resolver. If the data is being resolved through an interface or object reference // ensure the provided // source data can be converted otherwise ensure the types match exactly. if (_graphMethod.Parent.ObjectType.IsInterface || _graphMethod.Parent.ObjectType.IsClass) { if (!Validation.IsCastable(sourceData.GetType(), _graphMethod.Parent.ObjectType)) { context.Messages.Critical( "The source data provided to the field resolver " + $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( $"The property '{_graphMethod.InternalFullName}' expected source data that implements the interface " + $"'{_graphMethod.Parent.ObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' which " + "is not compatible.")); return; } } else if (sourceData.GetType() != _graphMethod.Parent.ObjectType) { context.Messages.Critical( "The source data provided to the field resolver " + $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( $"The property '{_graphMethod.InternalFullName}' expected source data of type " + $"'{_graphMethod.Parent.ObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' " + "which is not compatible.")); return; } try { var invoker = InstanceFactory.CreateInstanceMethodInvoker(_graphMethod.Method); var invokeReturn = invoker(sourceData, new object[0]); if (_graphMethod.IsAsyncField) { if (invokeReturn is Task task) { await task.ConfigureAwait(false); if (task.IsFaulted) { throw task.UnwrapException(); } invokeReturn = task.ResultOfTypeOrNull(_graphMethod.ExpectedReturnType); } else { context.Messages.Critical( "The source data provided to the field resolver " + $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( $"The method '{_graphMethod.Route.Path}' is defined " + $"as asyncronous but it did not return a {typeof(Task)}.")); invokeReturn = null; } } context.Result = invokeReturn; } catch (Exception ex) { context.Messages.Critical( $"An unknown error occured atttempting to resolve the field '{_graphMethod.Route.Path}'. See exception for details.", Constants.ErrorCodes.UNHANDLED_EXCEPTION, context.Request.Origin, ex); } }
/// <summary> /// Processes the given <see cref="IGraphFieldRequest" /> against this instance /// performing the operation as defined by this entity and generating a response. /// </summary> /// <param name="context">The field context containing the necessary data to resolve /// the field and produce a reslt.</param> /// <param name="cancelToken">The cancel token monitoring the execution of a graph request.</param> /// <returns>Task<IGraphPipelineResponse>.</returns> public Task Resolve(FieldResolutionContext context, CancellationToken cancelToken = default) { context.Result = _dataObject ?? new object(); return(Task.CompletedTask); }