Пример #1
0
        /// <summary>
        /// Type extensions used as batch methods required a speceial input and output signature for the runtime
        /// to properly supply and retrieve data from the batch. This method ensures the signature coorisponds to those requirements or
        /// throws an exception indicating the problem if one is found.
        /// </summary>
        private void ValidateBatchMethodSignatureOrThrow()
        {
            if (this.Mode != FieldResolutionMode.Batch)
            {
                return;
            }

            // the method MUST accept a parameter of type IEnumerable<TypeToExtend> in its signature somewhere
            // when declared in batch mode
            var requiredEnumerable = typeof(IEnumerable <>).MakeGenericType(this.SourceObjectType);

            if (this.Arguments.All(arg => arg.DeclaredArgumentType != requiredEnumerable))
            {
                throw new GraphTypeDeclarationException(
                          $"Invalid batch method signature. The field '{this.InternalFullName}' declares itself as batch method but does not accept a batch " +
                          $"of data as an input parameter. This method must accept a parameter of type '{requiredEnumerable.FriendlyName()}' somewhere in its method signature to " +
                          $"be used as a batch extension for the type '{this.SourceObjectType.FriendlyName()}'.");
            }

            var declaredType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType);

            if (declaredType == typeof(IGraphActionResult))
            {
                return;
            }

            // when a batch method doesn't return an action result, indicating the developer
            // opts to specify his return types explicitly; ensure that their chosen return type is a dictionary
            // keyed on the type being extended allowing the runtime to seperate the batch
            // for proper segmentation in the object graph.
            // --
            // when the return type is a graph action this check is deferred after results of the batch are produced
            if (!BatchResultProcessor.IsBatchDictionaryType(declaredType, this.SourceObjectType, this.ObjectType))
            {
                throw new GraphTypeDeclarationException(
                          $"Invalid batch method signature. The field '{this.InternalFullName}' declares a return type of '{declaredType.FriendlyName()}', however; " +
                          $"batch methods must return either an '{typeof(IGraphActionResult).FriendlyName()}' or a dictionary keyed " +
                          "on the provided source data (e.g. 'IDictionary<SourceType, ResultsPerSourceItem>').");
            }

            // ensure any possible type declared via attribution matches the value type of the resultant dictionary
            // e.g.. if they supply a union for the field but declare a dictionary of IDictionary<T,K>
            // each member of the union must be castable to 'K' in order for the runtime to properly seperate
            // and process the batch results
            var dictionaryValue = GraphValidation.EliminateWrappersFromCoreType(declaredType.GetValueTypeOfDictionary());

            foreach (var type in this.PossibleTypes)
            {
                if (!Validation.IsCastable(type, dictionaryValue))
                {
                    throw new GraphTypeDeclarationException(
                              $"The field '{this.InternalFullName}' returns '{this.ObjectType.FriendlyName()}' and declares a possible type of '{type.FriendlyName()}' " +
                              $"but that type is not castable to '{this.ObjectType.FriendlyName()}' and therefore not returnable by this field. Due to the strongly-typed nature of C# any possible type on a field " +
                              "must be castable to the type of the field in order to ensure its not inadvertantly nulled out during processing. If this field returns a union " +
                              $"of multiple, disperate types consider returning '{typeof(object).Name}' from the field to ensure each possible return type can be successfully processed.");
                }
            }
        }
Пример #2
0
        /// <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()})");
        }
Пример #3
0
        public void IsBatchDictionary(Type typeToTest, Type typeOfKey, Type typeOfResult, bool expectedResult)
        {
            var isValid = BatchResultProcessor.IsBatchDictionaryType(typeToTest, typeOfKey, typeOfResult);

            Assert.AreEqual(expectedResult, isValid);
        }