Example #1
0
        /// <summary>
        /// Determines whether the the provided type to check is IDictionary{ExpectedKey, AnyValue} ensuring that it can be
        /// used as an input to a batch processor that supplies data to multiple waiting items.
        /// </summary>
        /// <param name="typeToCheck">The type being checked.</param>
        /// <param name="expectedKeyType">The expected key type.</param>
        /// <param name="batchedItemType">Type of the batched item. if the type to check returns an IEnumerable per
        /// key this type must represent the individual item of the set. (i.e. the 'T' of IEnumerable{T}).</param>
        /// <returns><c>true</c> if the type represents a dictionary keyed on the expected key type; otherwise, <c>false</c>.</returns>
        public static bool IsBatchDictionaryType(Type typeToCheck, Type expectedKeyType, Type batchedItemType)
        {
            if (typeToCheck == null || expectedKeyType == null || batchedItemType == null)
            {
                return(false);
            }

            var enumeratedType = typeToCheck.GetEnumerableUnderlyingType();

            if (enumeratedType == null || !enumeratedType.IsGenericType || typeof(KeyValuePair <,>) != enumeratedType.GetGenericTypeDefinition())
            {
                return(false);
            }

            var paramSet = enumeratedType.GetGenericArguments();

            if (paramSet.Length != 2)
            {
                return(false);
            }

            if (paramSet[0] != expectedKeyType)
            {
                return(false);
            }

            var unwrapped = GraphValidation.EliminateWrappersFromCoreType(paramSet[1]);

            if (unwrapped != batchedItemType)
            {
                return(false);
            }

            return(true);
        }
Example #2
0
        /// <summary>
        /// Adds the given type to the schema as a graph type or a registered controller depending
        /// on the type.
        /// </summary>
        /// <param name="type">The concrete type to add.</param>
        /// <param name="kind">The kind of graph type to create from the supplied concrete type. If not supplied the concrete type will
        /// attempt to auto assign a type of scalar, enum or object as necessary.</param>
        public void EnsureGraphType(Type type, TypeKind?kind = null)
        {
            if (Validation.IsCastable <GraphController>(type))
            {
                if (GraphQLProviders.TemplateProvider.ParseType(type) is IGraphControllerTemplate controllerDefinition)
                {
                    this.AddController(controllerDefinition);
                }

                return;
            }

            type = GraphValidation.EliminateWrappersFromCoreType(type);

            // if the type is already registered, early exit no point in running it through again
            var actualKind = GraphValidation.ResolveTypeKindOrThrow(type, kind);

            if (this.Schema.KnownTypes.Contains(type, actualKind))
            {
                return;
            }

            var maker = GraphQLProviders.GraphTypeMakerProvider.CreateTypeMaker(this.Schema, actualKind);

            if (maker != null)
            {
                var result = maker.CreateGraphType(type);
                if (result != null)
                {
                    this.Schema.KnownTypes.EnsureGraphType(result.GraphType, result.ConcreteType);
                    this.EnsureDependents(result);
                }
            }
        }
Example #3
0
        /// <summary>
        /// Ensures the provided <see cref="IGraphType" /> exists in this collection (adding it if it is missing)
        /// and that the given type reference is assigned to it. An exception will be thrown if the type reference is already assigned
        /// to a different <see cref="IGraphType" />. No dependents or additional types will be added.
        /// </summary>
        /// <param name="graphType">Type of the graph.</param>
        /// <param name="associatedType">The concrete type to associate to the graph type.</param>
        /// <returns><c>true</c> if type had to be added, <c>false</c> if it already existed in the collection.</returns>
        public bool EnsureGraphType(IGraphType graphType, Type associatedType = null)
        {
            Validation.ThrowIfNull(graphType, nameof(graphType));

            associatedType = GraphValidation.EliminateWrappersFromCoreType(associatedType);
            GraphValidation.EnsureValidGraphTypeOrThrow(associatedType);

            // attempt to create a relationship between the graph type and the associated type
            // the concrete type collection will throw an exception if that relationship fails or isnt
            // updated to the new data correctly.
            var justAdded = _graphTypesByName.TryAdd(graphType.Name, graphType);

            _concreteTypes.EnsureRelationship(graphType, associatedType);
            _extendableGraphTypeTracker.MonitorGraphType(graphType);

            // dequeue and add any extension fields if present
            if (associatedType != null)
            {
                var unregisteredFields = _typeQueue.DequeueFields(associatedType);
                if (graphType is IExtendableGraphType objType)
                {
                    foreach (var field in unregisteredFields)
                    {
                        _extendableGraphTypeTracker.AddFieldExtension(objType, field);
                    }
                }
            }

            return(justAdded);
        }
Example #4
0
        /// <summary>
        /// Builds out this field template parsing out the required type declarations unions, nameing scheme etc.  This method should be
        /// overridden in any child classes for additional decalration requirements.
        /// </summary>
        protected override void ParseTemplateDefinition()
        {
            // ensure type extension right out of the gate
            // the field can't be built otherwise
            _typeAttrib = this.SingleAttributeOfTypeOrDefault <TypeExtensionAttribute>();
            if (_typeAttrib == null)
            {
                return;
            }

            _sourceType = _typeAttrib.TypeToExtend;

            base.ParseTemplateDefinition();

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

            if (returnType != typeof(IGraphActionResult))
            {
                // inspect the return type, if its a valid dictionary extract the return type from the value
                // and set the type modifiers and method type based on the value of each dictionary entry
                if (_typeAttrib.ExecutionMode == FieldResolutionMode.Batch)
                {
                    returnType          = returnType.GetValueTypeOfDictionary();
                    this.ObjectType     = GraphValidation.EliminateWrappersFromCoreType(returnType);
                    this.TypeExpression = GraphValidation.GenerateTypeExpression(returnType, this);
                    this.PossibleTypes.Insert(0, this.ObjectType);
                }
            }
        }
Example #5
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.");
                }
            }
        }
        /// <summary>
        /// Parses the primary metadata about the method.
        /// </summary>
        public void Parse()
        {
            DirectiveLifeCycle lifeCycle;

            switch (this.Method.Name)
            {
            case Constants.ReservedNames.DIRECTIVE_BEFORE_RESOLUTION_METHOD_NAME:
                lifeCycle = DirectiveLifeCycle.BeforeResolution;
                break;

            case Constants.ReservedNames.DIRECTIVE_AFTER_RESOLUTION_METHOD_NAME:
                lifeCycle = DirectiveLifeCycle.AfterResolution;
                break;

            default:
                return;
            }

            this.IsValidDirectiveMethod = (this.Method.ReturnType == typeof(IGraphActionResult) || this.Method.ReturnType == typeof(Task <IGraphActionResult>)) &&
                                          !this.Method.IsGenericMethod;

            this.Description  = this.Method.SingleAttributeOrDefault <DescriptionAttribute>()?.Description;
            this.DeclaredType = this.Method.ReturnType;
            this.IsAsyncField = Validation.IsCastable <Task>(this.Method.ReturnType);

            // is the method asyncronous? if so ensure that a Task<T> is returned
            // and not an empty task
            if (this.IsAsyncField && this.Method.ReturnType.IsGenericType)
            {
                // for any ssync field attempt to pull out the T in Task<T>
                var genericArgs = this.Method.ReturnType.GetGenericArguments();
                if (genericArgs.Any())
                {
                    this.DeclaredType = genericArgs[0];
                }
            }

            this.ObjectType     = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredType);
            this.LifeCycle      = lifeCycle;
            this.Route          = this.GenerateRoute();
            this.TypeExpression = new GraphTypeExpression(this.ObjectType.FriendlyName());

            // parse all input parameters into the method
            foreach (var parameter in this.Method.GetParameters())
            {
                var argTemplate = new GraphFieldArgumentTemplate(this, parameter);
                argTemplate.Parse();
                _arguments.Add(argTemplate);
            }

            this.MethodSignature = this.GenerateMethodSignatureString();

            this.ExpectedReturnType = GraphValidation.EliminateWrappersFromCoreType(
                this.DeclaredType,
                false,
                true,
                false);
        }
Example #7
0
        /// <summary>
        /// Updates this instance with a piece of data recieved from the completion of a user resolver function.
        /// This method performs no validation or resolution of the data, it simply assigns it as a value that was recieved
        /// from a resolver function and attaches it to this instance.
        /// </summary>
        /// <param name="data">The data.</param>
        public virtual void AssignResult(object data)
        {
            _childListItems = null;
            _childFields    = null;

            this.ResultData = data;
            this.SetStatus(FieldItemResolutionStatus.ResultAssigned);

            // initialize the children of this instance from the assigned result
            if (this.IsListField)
            {
                if (this.ResultData == null)
                {
                    return;
                }

                if (!GraphValidation.IsValidListType(this.ResultData.GetType()))
                {
                    return;
                }

                if (!(this.ResultData is IEnumerable arraySource))
                {
                    return;
                }

                _childListItems = new List <GraphDataItem>();

                // this instances's type expression is a list (or else we wouldnt be rendering out children)
                // strip out that outer most list component
                // to represent what each element of said list should be so that the rule
                // processor can validate the item as its own entity
                var childTypeExpression = this.TypeExpression.UnWrapExpression(MetaGraphTypes.IsList);

                var path  = this.Path.Clone();
                int index = 0;

                foreach (var item in arraySource)
                {
                    var indexedPath = path.Clone();
                    indexedPath.AddArrayIndex(index++);
                    var childItem = new GraphDataItem(this.FieldContext, item, indexedPath)
                    {
                        TypeExpression = childTypeExpression.Clone(),
                    };

                    this.AddListItem(childItem);

                    // child items are immediately resolved
                    // using the enumerated source data from the resolver that supplied data
                    // to this parent item.
                    // that is to say that no child resolver is needed to be processed to
                    // retrieve data for each child individually.
                    childItem.AssignResult(item);
                    childItem.SetStatus(this.Status);
                }
            }
        }
Example #8
0
        /// <summary>
        /// Updates the field resolver used by this graph field.
        /// </summary>
        /// <param name="newResolver">The new resolver this field should use.</param>
        /// <param name="mode">The new resolution mode used by the runtime to invoke the resolver.</param>
        public void UpdateResolver(IGraphFieldResolver newResolver, FieldResolutionMode mode)
        {
            this.Resolver = newResolver;
            this.Mode     = mode;

            var unrwrappedType = GraphValidation.EliminateWrappersFromCoreType(this.Resolver?.ObjectType);

            this.IsLeaf = this.Resolver?.ObjectType != null && GraphQLProviders.ScalarProvider.IsLeaf(unrwrappedType);
        }
Example #9
0
        /// <summary>
        /// When overridden in a child class this method builds out the template according to its own individual requirements.
        /// </summary>
        protected override void ParseTemplateDefinition()
        {
            base.ParseTemplateDefinition();

            this.ExpectedReturnType = GraphValidation.EliminateWrappersFromCoreType(
                this.DeclaredReturnType,
                false,
                true,
                false);
        }
        /// <summary>
        /// When overridden in a child class, this metyhod builds the route that will be assigned to this method
        /// using the implementation rules of the concrete type.
        /// </summary>
        /// <returns>GraphRoutePath.</returns>
        protected override GraphFieldPath GenerateFieldPath()
        {
            // an object method cannot contain any route pathing or nesting like controller methods can
            // before creating hte route, ensure that the declared name, by itself, is valid for graphql
            var graphName = this.Method.SingleAttributeOrDefault <GraphFieldAttribute>()?.Template?.Trim() ?? Constants.Routing.ACTION_METHOD_META_NAME;

            graphName = graphName.Replace(Constants.Routing.ACTION_METHOD_META_NAME, this.Method.Name).Trim();

            GraphValidation.EnsureGraphNameOrThrow(this.InternalFullName, graphName);
            return(new GraphFieldPath(GraphFieldPath.Join(this.Parent.Route.Path, graphName)));
        }
        /// <summary>
        /// Invokes this middleware component allowing it to perform its work against the supplied context.
        /// </summary>
        /// <param name="context">The context containing the request passed through the pipeline.</param>
        /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param>
        /// <param name="cancelToken">The cancel token.</param>
        /// <returns>Task.</returns>
        public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvocationDelegate <GraphFieldExecutionContext> next, CancellationToken cancelToken)
        {
            // ensure that the data items on teh request match the field they are being executed against
            var field = context.Field;
            var expectedFieldGraphType = _schema.KnownTypes.FindGraphType(field);
            var dataSource             = context.Request.DataSource;

            // ensure the source being supplied
            // matches the expected source of the field being resolved
            if (context.InvocationContext.ExpectedSourceType != null)
            {
                var expectedSourceType = context.InvocationContext.ExpectedSourceType;
                var actualSourceType   = GraphValidation.EliminateWrappersFromCoreType(dataSource.Value.GetType());
                if (expectedSourceType != actualSourceType)
                {
                    var expectedSourceGraphType = _schema.KnownTypes.FindGraphType(expectedSourceType, TypeKind.OBJECT);
                    var analysis = _schema.KnownTypes.AnalyzeRuntimeConcreteType(expectedSourceGraphType, actualSourceType);
                    if (!analysis.ExactMatchFound)
                    {
                        throw new GraphExecutionException(
                                  $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                                  $"a source item of type '{dataSource.Value.GetType().FriendlyName()}' which could not be coerced " +
                                  $"to '{context.InvocationContext.ExpectedSourceType}' as requested by the target graph type '{expectedFieldGraphType.Name}'.");
                    }

                    if (context.Field.Mode == FieldResolutionMode.Batch && !(dataSource.GetType() is IEnumerable))
                    {
                        throw new GraphExecutionException(
                                  $"Operation failed. The field execution context for '{field.Route.Path}' was executed in batch mode " +
                                  $"but was not passed an {nameof(IEnumerable)} for its source data.");
                    }
                }
            }

            if (dataSource.Items.Count == 0)
            {
                var expected = context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem ? "1" :
                               "at least 1";

                throw new GraphExecutionException(
                          $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                          $"0 items but expected {expected}.  (Field Mode: {field.Mode.ToString()})");
            }

            if (context.InvocationContext.Field.Mode == FieldResolutionMode.PerSourceItem &&
                dataSource.Items.Count != 1)
            {
                throw new GraphExecutionException(
                          $"Operation failed. The field execution context for '{field.Route.Path}' was passed " +
                          $"{dataSource.Items.Count} items to resolve but expected 1. (Field Mode: {field.Mode.ToString()})");
            }

            return(next(context, cancelToken));
        }
        /// <summary>
        /// Parses the template contents according to the rules of the template.
        /// </summary>
        public virtual void Parse()
        {
            this.DeclaredArgumentType = this.Parameter.ParameterType;
            this.ObjectType           = GraphValidation.EliminateWrappersFromCoreType(this.Parameter.ParameterType);

            // set the name
            _fieldDeclaration = this.Parameter.SingleAttributeOrDefault <FromGraphQLAttribute>();
            string name = null;

            if (_fieldDeclaration != null)
            {
                name = _fieldDeclaration?.ArgumentName?.Trim();
            }

            if (string.IsNullOrWhiteSpace(name))
            {
                name = Constants.Routing.PARAMETER_META_NAME;
            }

            name       = name.Replace(Constants.Routing.PARAMETER_META_NAME, this.Parameter.Name);
            this.Route = new GraphArgumentFieldPath(this.Parent.Route, name);

            this.Description = this.Parameter.SingleAttributeOrDefault <DescriptionAttribute>()?.Description?.Trim();

            if (this.Parameter.HasDefaultValue && this.Parameter.DefaultValue != null)
            {
                // enums will present their default value as a raw int
                if (this.ObjectType.IsEnum)
                {
                    this.DefaultValue = Enum.ToObject(this.ObjectType, this.Parameter.DefaultValue);
                }
                else
                {
                    this.DefaultValue = this.Parameter.DefaultValue;
                }
            }

            // set appropriate meta data about this parameter for inclusion in the type system
            this.TypeExpression = GraphValidation.GenerateTypeExpression(this.DeclaredArgumentType, this);
            this.TypeExpression = this.TypeExpression.CloneTo(GraphTypeNames.ParseName(this.ObjectType, TypeKind.INPUT_OBJECT));

            // when this argument accepts the same data type as the data returned by its owners target source type
            // i.e. if the source data supplied to the field for resolution is the same as this argument
            // then assume this argument is to contain the source data
            // since the source data will be an OBJECT type (not INPUT_OBJECT) there is no way the user could have supplied it
            if (this.IsSourceDataArgument())
            {
                this.ArgumentModifiers = this.ArgumentModifiers
                                         | GraphArgumentModifiers.ParentFieldResult
                                         | GraphArgumentModifiers.Internal;
            }
        }
        public void IsValidGraphType(Type inputType, bool isValidType)
        {
            var result = GraphValidation.IsValidGraphType(inputType);

            Assert.AreEqual(isValidType, result);
            if (!result)
            {
                Assert.Throws <GraphTypeDeclarationException>(() =>
                {
                    GraphValidation.IsValidGraphType(inputType, true);
                });
            }
        }
 /// <summary>
 /// Determines if the given concrete type is associated to a graph type of the supplied kind. This
 /// method will perform a <see cref="TypeKind"/> coersion if possible.
 /// </summary>
 /// <param name="type">The concrete type to search for.</param>
 /// <param name="kind">The graph type kind to look for.</param>
 /// <returns><c>true</c> if a graph type exists, <c>false</c> otherwise.</returns>
 public bool Contains(Type type, TypeKind?kind = null)
 {
     type = GraphQLProviders.ScalarProvider.EnsureBuiltInTypeReference(type);
     if (_graphTypesByConcreteType.TryGetValue(type, out var typeSet))
     {
         var resolvedKind = kind ?? GraphValidation.ResolveTypeKind(type);
         return(typeSet.ContainsKey(resolvedKind));
     }
     else
     {
         return(false);
     }
 }
Example #15
0
 /// <summary>
 /// Retrieves the concrete types that this instance may return or make use of in a graph query.
 /// </summary>
 /// <returns>IEnumerable&lt;Type&gt;.</returns>
 public IEnumerable <DependentType> RetrieveRequiredTypes()
 {
     if (this.ArgumentModifiers.IsSourceParameter())
     {
         // source parameters should not be injected into the object graph
         // so they have no dependents
         return(Enumerable.Empty <DependentType>());
     }
     else
     {
         var expectedTypeKind = GraphValidation.ResolveTypeKind(this.ObjectType, TypeKind.INPUT_OBJECT);
         return(new DependentType(this.ObjectType, expectedTypeKind).AsEnumerable());
     }
 }
        public void GenerateTypeExpression(
            Type type,
            string expectedExpression,
            bool definesDefaultValue,
            GTW[] wrappers)
        {
            var mock = new Mock <IGraphTypeExpressionDeclaration>();

            mock.Setup(x => x.HasDefaultValue).Returns(definesDefaultValue);
            mock.Setup(x => x.TypeWrappers).Returns(wrappers);

            var typeExpression = GraphValidation.GenerateTypeExpression(type, mock.Object);

            Assert.AreEqual(expectedExpression, typeExpression.ToString());
        }
Example #17
0
        /// <summary>
        /// Determines whether the given container could be used as a graph field either because it is
        /// explictly declared as such or that it conformed to the required parameters of being
        /// a field.
        /// </summary>
        /// <param name="memberInfo">The member information to check.</param>
        /// <returns>
        ///   <c>true</c> if the info represents a possible graph field; otherwise, <c>false</c>.</returns>
        protected virtual bool CouldBeGraphField(MemberInfo memberInfo)
        {
            // always skip those marked as such regardless of anything else
            if (memberInfo.HasAttribute <GraphSkipAttribute>())
            {
                return(false);
            }

            // when the member declares any known attribute in the library include it
            // and allow it to generate validation failures if its not properly constructed
            if (memberInfo.SingleAttributeOfTypeOrDefault <GraphFieldAttribute>() != null)
            {
                return(true);
            }

            switch (memberInfo)
            {
            case MethodInfo mi:
                if (!GraphValidation.IsValidGraphType(mi.ReturnType, false))
                {
                    return(false);
                }
                if (mi.GetParameters().Any(x => !GraphValidation.IsValidGraphType(x.ParameterType, false)))
                {
                    return(false);
                }
                break;

            case PropertyInfo pi:
                if (pi.GetGetMethod() == null)
                {
                    return(false);
                }
                if (pi.GetIndexParameters().Length > 0)
                {
                    return(false);
                }

                if (!GraphValidation.IsValidGraphType(pi.PropertyType, false))
                {
                    return(false);
                }
                break;
            }

            return(true);
        }
        /// <summary>
        /// Attempts to find the graph type of a given kind for the supplied concrete type. Returns null if no type is found. This
        /// method will perform a <see cref="TypeKind"/> coersion if possible.
        /// </summary>
        /// <param name="type">The concrete type to search with.</param>
        /// <param name="kind">The kind of graph type to search for. If not supplied the schema will attempt to automatically
        /// resolve the correct kind from the given <see cref="Type"/>.</param>
        /// <returns>IGraphType.</returns>
        public IGraphType FindGraphType(Type type, TypeKind?kind = null)
        {
            Validation.ThrowIfNull(type, nameof(type));

            type = GraphQLProviders.ScalarProvider.EnsureBuiltInTypeReference(type);
            var resolvedKind = GraphValidation.ResolveTypeKind(type, kind);

            if (_graphTypesByConcreteType.TryGetValue(type, out var typeSet))
            {
                if (typeSet.TryGetValue(resolvedKind, out var graphType))
                {
                    return(graphType);
                }
            }

            return(null);
        }
        /// <summary>
        /// When overridden in a child class this method builds out the template according to its own individual requirements.
        /// </summary>
        protected override void ParseTemplateDefinition()
        {
            base.ParseTemplateDefinition();

            // parse all input parameters from the method signature
            foreach (var parameter in this.Method.GetParameters())
            {
                var argTemplate = this.CreateGraphFieldArgument(parameter);
                argTemplate.Parse();
                _arguments.Add(argTemplate);
            }

            this.ExpectedReturnType = GraphValidation.EliminateWrappersFromCoreType(
                this.DeclaredReturnType,
                false,
                true,
                false);
        }
        /// <summary>
        /// Parses the provided type, extracting the metadata to used in type generation for the object graph.
        /// </summary>
        /// <param name="objectType">The type of the object to parse.</param>
        /// <param name="kind">The kind of graph type to parse for.</param>
        /// <returns>IGraphTypeTemplate.</returns>
        public IGraphTypeTemplate ParseType(Type objectType, TypeKind?kind = null)
        {
            Validation.ThrowIfNull(objectType, nameof(objectType));

            var typeKind = GraphValidation.ResolveTypeKind(objectType, kind);
            var typeKey  = Tuple.Create(typeKind, objectType);

            if (_knownObjects.TryGetValue(typeKey, out var template) && this.CacheTemplates)
            {
                return(template);
            }

            if (GraphQLProviders.ScalarProvider.IsScalar(objectType))
            {
                throw new GraphTypeDeclarationException(
                          $"The type '{objectType.FriendlyName()}' is a known scalar type. Scalars must be explicitly defined and cannot be templated.",
                          objectType);
            }

            if (Validation.IsCastable <IGraphUnionProxy>(objectType))
            {
                throw new GraphTypeDeclarationException(
                          $"The union proxy '{objectType.FriendlyName()}' cannot be directly parsed as a graph type. Double check " +
                          "your field attribute declarations'.",
                          objectType);
            }

            GraphValidation.IsValidGraphType(objectType, true);

            template = this.MakeTemplate(objectType, typeKind);
            template.Parse();
            template.ValidateOrThrow();

            if (this.CacheTemplates)
            {
                _knownObjects.TryAdd(typeKey, template);
            }

            return(template);
        }
Example #21
0
        /// <summary>
        /// Validates the completed field context to ensure it is "correct" against the specification before finalizing its reslts.
        /// </summary>
        /// <param name="context">The context containing the resolved field.</param>
        /// <returns><c>true</c> if the node is valid, <c>false</c> otherwise.</returns>
        public override bool Execute(FieldValidationContext context)
        {
            var dataObject = context.ResultData;

            // did they forget to await a task and accidentally returned Task<T> from their resolver?
            if (dataObject is Task)
            {
                this.ValidationError(
                    context,
                    $"A field resolver for '{context.FieldPath}' yielded an invalid data object. See exception " +
                    "for details. ",
                    new GraphExecutionException(
                        $"The field '{context.FieldPath}' yielded a {nameof(Task)} as its result but expected a value. " +
                        "Did you forget to await an async method?"));

                context.DataItem.InvalidateResult();
            }
            else
            {
                // is the concrete type of the data item not known to the schema?
                var strippedSourceType = GraphValidation.EliminateWrappersFromCoreType(dataObject.GetType());
                var graphType          = context.Schema.KnownTypes.FindGraphType(strippedSourceType);
                if (graphType == null)
                {
                    this.ValidationError(
                        context,
                        $"A field resolver for '{context.Field.Route.Path}' generated a result " +
                        "object type not known to the target schema. See exception for " +
                        "details",
                        new GraphExecutionException(
                            $"The class '{strippedSourceType.FriendlyName()}' is not not mapped to a graph type " +
                            "on the target schema."));

                    context.DataItem.InvalidateResult();
                }
            }

            return(true);
        }
        /// <summary>
        /// Validates the completed field context to ensure it is "correct" against the specification before finalizing its reslts.
        /// </summary>
        /// <param name="context">The context containing the resolved field.</param>
        /// <returns><c>true</c> if the node is valid, <c>false</c> otherwise.</returns>
        public override bool Execute(FieldValidationContext context)
        {
            var dataObject             = context.ResultData;
            var dataItemTypeExpression = context.DataItem.TypeExpression;

            // did they forget to await a task and accidentally returned Task<T> from their resolver?
            // put in a special error message for better feed back
            if (dataObject is Task)
            {
                this.ValidationError(
                    context,
                    $"A field resolver for '{context.FieldPath}' yielded an invalid data object. See exception " +
                    "for details. ",
                    new GraphExecutionException(
                        $"The field '{context.FieldPath}' yielded a {nameof(Task)} as its result but expected a value. " +
                        "Did you forget to await an async method?"));

                context.DataItem.InvalidateResult();
                return(true);
            }

            var expectedGraphType = context.Schema?.KnownTypes.FindGraphType(context.Field);

            if (expectedGraphType == null)
            {
                this.ValidationError(
                    context,
                    $"The graph type for field '{context.FieldPath}' ({dataItemTypeExpression?.TypeName}) does not exist on the target schema. The field" +
                    "cannot be properly evaluated.");

                context.DataItem.InvalidateResult();
                return(true);
            }

            // virtual graph types aren't real, they can be safely skipped
            if (expectedGraphType.IsVirtual)
            {
                return(true);
            }

            var rootSourceType = GraphValidation.EliminateWrappersFromCoreType(dataObject.GetType());

            // Check that the actual .NET type of the result data IS (or can be cast to) the expected .NET
            // type for the graphtype of the field being checked
            var analysisResult = context.Schema.KnownTypes.AnalyzeRuntimeConcreteType(expectedGraphType, rootSourceType);

            if (!analysisResult.ExactMatchFound)
            {
                string foundTypeNames;
                if (!analysisResult.FoundTypes.Any())
                {
                    foundTypeNames = "~none~";
                }
                else
                {
                    foundTypeNames = string.Join(", ", analysisResult.FoundTypes.Select(x => x.FriendlyName()));
                }

                this.ValidationError(
                    context,
                    $"A field resolver for '{context.Field.Route.Path}' generated a result " +
                    "object type not known to the target schema. See exception for " +
                    "details",
                    new GraphExecutionException(
                        $"For target field of '{context.FieldPath}' (Graph Type: {expectedGraphType.Name}, Kind: {expectedGraphType.Kind}), a supplied object " +
                        $"of class '{rootSourceType.FriendlyName()}' attempted to fill the request but graphql was not able to determine which " +
                        $"of the matched concrete types to use and cannot resolve the field. Matched Types: [{foundTypeNames}]"));

                context.DataItem.InvalidateResult();
            }
            else
            {
                // once the type is confirmed, confirm the actual value
                // for scenarios such as where a number may be masqurading as an enum but the enum doesn't define
                // a label for said number.
                var isValidValue = expectedGraphType.ValidateObject(dataObject);
                if (!isValidValue)
                {
                    string actual = string.Empty;
                    if (expectedGraphType is ObjectGraphType)
                    {
                        actual = dataObject.GetType().Name;
                    }
                    else
                    {
                        actual = dataObject.ToString();
                    }

                    this.ValidationError(
                        context,
                        $"A resolved value for field '{context.FieldPath}' does not match the required graph type. " +
                        $"Expected '{expectedGraphType.Name}' but got '{actual}'.");

                    context.DataItem.InvalidateResult();
                }
            }

            return(true);
        }
        public override void OnShapesInserted(List <Shape> affectedShapes)
        {
            base.OnShapesInserted(affectedShapes);

            if (affectedShapes.Count == 1 && affectedShapes.First() is Polyline)
            {
                var p   = affectedShapes.First() as Polyline;
                var cps = p.GetControlPointIds(ControlPointCapabilities.All);
                var p1  = p.GetControlPointPosition(cps.First());
                var p2  = p.GetControlPointPosition(cps.Last());

                Box b1 = null;
                Box b2 = null;

                foreach (var s in p.Diagram.Shapes.Except(p))
                {
                    if (b1 == null && s.ContainsPoint(p1.X, p1.Y))
                    {
                        b1 = s as Box;
                    }
                    else if (b2 == null && s.ContainsPoint(p2.X, p2.Y))
                    {
                        b2 = s as Box;
                    }
                }

                if (b1 != null && b2 != null && b1 != b2)
                {
                    var n1 = DiagramWrapper.GetNodeForShape(b1);
                    var n2 = DiagramWrapper.GetNodeForShape(b2);

                    if (!n2.Quest.IsLinksToEditable())
                    {
                        DeleteShapeOnDeselect(p);
                        var msg = $"Couldn't create link to {n2.Quest.Name} quest - links to this quest are editable only through code.";
                        MessageBox.Show(msg);
                        return;
                    }

                    var link = new Link(n1, n2);

                    if (Context.Flow.Graph.ExistsLink(link))
                    {
                        DeleteShapeOnDeselect(p);
                        var msg = $"Couldn't create link from {n1.Quest.Name} to {n2.Quest.Name} quest - link already exists.";
                        MessageBox.Show(msg);
                        return;
                    }

                    (var loopForms, var loop) = GraphValidation.LoopForms(Context.Flow.Graph, link);
                    if (loopForms)
                    {
                        DeleteShapeOnDeselect(p);
                        var loopStringRepresentation = string.Join(" - ", loop.Select(q => q.Quest.Name));
                        var msg = $"Couldn't create link from {n1.Quest.Name} to {n2.Quest.Name} quest - loop forms: {loopStringRepresentation}.";
                        MessageBox.Show(msg);
                        return;
                    }

                    DiagramWrapper.RegisterShapeForLink(p, link);

                    if (!n1.Quest.IsActive() || !n2.Quest.IsActive())
                    {
                        var command = new CompositeCommand();

                        void InitQuestActivationCommand(Quest quest) =>
                        command.AddCommand(
                            CommandsCreation.ActivateQuest(
                                quest,
                                Context.Flow.GetSectorForQuest(quest),
                                Context,
                                DiagramWrapper
                                )
                            );

                        if (!n1.Quest.IsActive())
                        {
                            InitQuestActivationCommand(n1.Quest);
                        }
                        if (!n2.Quest.IsActive())
                        {
                            InitQuestActivationCommand(n2.Quest);
                        }
                        command.AddCommand(CommandsCreation.AddLink(link, Context, DiagramWrapper));

                        Context.History.Do(command);
                    }
                    else
                    {
                        Context.History.Do(CommandsCreation.AddLink(link, Context, DiagramWrapper));
                    }
                }
                else
                {
                    DeleteShapeOnDeselect(p);
                }
            }
            else
            {
                DeleteShapesOnDeselect(affectedShapes);
            }
        }
 /// <summary>
 /// When overridden in a child class, allows the template to perform some final validation checks
 /// on the integrity of itself. An exception should be thrown to stop the template from being
 /// persisted if the object is unusable or otherwise invalid in the manner its been built.
 /// </summary>
 public void ValidateOrThrow()
 {
     GraphValidation.EnsureGraphNameOrThrow($"{_parentEnum.Name}", this.Name);
 }
Example #25
0
 /// <summary>
 /// Creates a <see cref="IGraphType" /> representing the union this field should masquerade as in the object graph.
 /// Returns null if no union is declared for this field.
 /// </summary>
 /// <returns>IGraphType.</returns>
 public virtual IEnumerable <DependentType> RetrieveRequiredTypes()
 {
     // a base field knows, at most, about the return object type it is dependent on
     return(this.PossibleTypes?.Select(x => new DependentType(x, GraphValidation.ResolveTypeKind(x, this.Kind)))
            ?? Enumerable.Empty <DependentType>());
 }
Example #26
0
        /// <summary>
        /// When overridden in a child class, allows the template to perform some final validation checks
        /// on the integrity of itself. An exception should be thrown to stop the template from being
        /// persisted if the object is unusable or otherwise invalid in the manner its been built.
        /// </summary>
        public override void ValidateOrThrow()
        {
            base.ValidateOrThrow();

            if (this.DeclaredReturnType == typeof(void))
            {
                throw new GraphTypeDeclarationException($"The graph field '{this.InternalFullName}' has a void return. All graph fields must return something.");
            }

            if (this.IsAsyncField)
            {
                // account for a mistake by the developer in using a potential return type of just "Task" instead of Task<T>
                var genericArgs = this.DeclaredReturnType.GetGenericArguments();
                if (genericArgs.Length != 1)
                {
                    throw new GraphTypeDeclarationException(
                              $"The field  '{this.InternalFullName}' defines a return type of'{typeof(Task).Name}' but " +
                              "defines no contained return type for the resultant model object yielding a void return after " +
                              "completion of the task. All graph methods must return a single model object. Consider using " +
                              $"'{typeof(Task<>).Name}' instead for asyncronous methods");
                }
            }

            if (this.UnionProxy != null)
            {
                GraphValidation.EnsureGraphNameOrThrow($"{this.InternalFullName}[{nameof(GraphFieldAttribute)}][{nameof(IGraphUnionProxy)}]", this.UnionProxy.Name);
                if (this.UnionProxy.Types.Count < 1)
                {
                    throw new GraphTypeDeclarationException(
                              $"The field '{this.InternalFullName}' declares union type of '{this.UnionProxy.Name}' " +
                              $"but that type includes 0 possible types in the union. Unions require 1 or more possible types. Add additional types" +
                              "or remove the union.");
                }
            }

            // ensure the object type returned by the graph field is set correctly
            bool returnsActionResult = Validation.IsCastable <IGraphActionResult>(this.ObjectType);
            var  enforceUnionRules   = this.UnionProxy != null;

            if (this.PossibleTypes.Count == 0)
            {
                throw new GraphTypeDeclarationException(
                          $"The field '{this.InternalFullName}' declared no possible return types either on its attribute declarations or as the " +
                          "declared return type for the field. GraphQL requires the type information be known " +
                          $"to setup the schema and client tooling properly. If this field returns a '{nameof(IGraphActionResult)}' you must " +
                          "provide a graph field declaration attribute and add at least one type; be that a concrete type, an interface or a union.");
            }

            // validate each type in the list for "correctness"
            // Possible Types must conform to the rules of those required by sub type declarations of unions and interfaces
            // interfaces: https://graphql.github.io/graphql-spec/June2018/#sec-Interfaces
            // unions: https://graphql.github.io/graphql-spec/June2018/#sec-Unions
            foreach (var type in this.PossibleTypes)
            {
                if (enforceUnionRules)
                {
                    if (GraphQLProviders.ScalarProvider.IsScalar(type))
                    {
                        throw new GraphTypeDeclarationException(
                                  $"The field '{this.InternalFullName}' declares union with a possible type of '{type.FriendlyName()}' " +
                                  $"but that type is a scalar. Scalars cannot be included in a field's possible type collection, only object types can.");
                    }

                    if (type.IsInterface)
                    {
                        throw new GraphTypeDeclarationException(
                                  $"The field '{this.InternalFullName}'  declares union with  a possible type of '{type.FriendlyName()}' " +
                                  $"but that type is an interface. Interfaces cannot be included in a field's possible type collection, only object types can.");
                    }

                    if (type.IsEnum)
                    {
                        throw new GraphTypeDeclarationException(
                                  $"The field '{this.InternalFullName}' declares a union with a possible type of '{type.FriendlyName()}' " +
                                  "but that type is an enum. Only concrete, non-abstract classes may be used.  Value types, such as structs or enumerations, are not allowed.");
                    }

                    if (!type.IsClass)
                    {
                        throw new GraphTypeDeclarationException(
                                  $"The field '{this.InternalFullName}' returns an interface named '{this.ObjectType.FriendlyName()}' and declares a possible type of '{type.FriendlyName()}' " +
                                  "but that type is not a valid class. Only concrete, non-abstract classes may be used.  Value types, such as structs or enumerations, are also not allowed.");
                    }
                }

                foreach (var invalidFieldType in Constants.InvalidFieldTemplateTypes)
                {
                    if (Validation.IsCastable(type, invalidFieldType))
                    {
                        throw new GraphTypeDeclarationException(
                                  $"The field '{this.InternalFullName}' declares a possible return type of '{type.FriendlyName()}' " +
                                  $"but that type inherits from '{invalidFieldType.FriendlyName()}' which is a reserved type declared by the graphql-aspnet library. This type cannot cannot be returned by a graphql field.");
                    }
                }

                // to ensure an object isn't arbitrarly returned as null and lost
                // ensure that the any possible type returned from this field is returnable AS the type this field declares
                // as its return type. In doing this we know that, potentially, an object returned by this
                // field "could" cast to the return type and allow field execution to continue.
                //
                // This is a helpful developer safety check, not a complete guarantee as concrete types for interface
                // declarations are not required at this stage
                //
                // batch processed fields are not subject to this restriction
                if (!returnsActionResult && this.Mode == FieldResolutionMode.PerSourceItem && !Validation.IsCastable(type, this.ObjectType))
                {
                    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.");
                }
            }

            foreach (var argument in this.Arguments)
            {
                argument.ValidateOrThrow();
            }

            if (this.Complexity.HasValue && this.Complexity < 0)
            {
                throw new GraphTypeDeclarationException(
                          $"The field '{this.InternalFullName}' declares a complexity value of " +
                          $"`{this.Complexity.Value}`. The complexity factor must be greater than or equal to 0.");
            }

            this.ValidateBatchMethodSignatureOrThrow();
        }
        /// <summary>
        /// Adds a new scalar type to the global collection of recognized scalar values. These graph types are used as
        /// singleton instances across all schema's and all querys.
        /// </summary>
        /// <param name="graphType">The scalar type to register.</param>
        public void RegisterCustomScalar(IScalarGraphType graphType)
        {
            Validation.ThrowIfNull(graphType, nameof(graphType));
            if (string.IsNullOrWhiteSpace(graphType.Name))
            {
                throw new GraphTypeDeclarationException(
                          "The scalar must supply a name that is not null or whitespace.");
            }

            if (!GraphValidation.IsValidGraphName(graphType.Name))
            {
                throw new GraphTypeDeclarationException(
                          $"The scalar must supply a name that that conforms to the standard rules for GraphQL. (Regex: {Constants.RegExPatterns.NameRegex})");
            }

            if (graphType.Kind != TypeKind.SCALAR)
            {
                throw new GraphTypeDeclarationException(
                          $"The scalar's type kind must be set to '{nameof(TypeKind.SCALAR)}'.");
            }

            if (graphType.ObjectType == null)
            {
                throw new GraphTypeDeclarationException(
                          $"The scalar must supply a value for '{nameof(graphType.ObjectType)}', is cannot be null.");
            }

            if (graphType.SourceResolver == null)
            {
                throw new GraphTypeDeclarationException(
                          $"The scalar must supply a value for '{nameof(graphType.SourceResolver)}' that can convert data from a " +
                          $"query into the primary object type of '{graphType.ObjectType.FriendlyName()}'.");
            }

            if (graphType.ValueType == ScalarValueType.Unknown)
            {
                throw new GraphTypeDeclarationException(
                          $"The scalar must supply a value for '{nameof(graphType.ValueType)}'. This lets the validation engine " +
                          "know what data types submitted on a user query could be parsed into a value for this scale.");
            }

            if (graphType.OtherKnownTypes == null)
            {
                throw new GraphTypeDeclarationException(
                          $"Custom scalars must supply a value for '{nameof(graphType.OtherKnownTypes)}', it cannot be null. " +
                          "Use an empty list if there are no other known types.");
            }

            var isAScalarAlready = this.IsScalar(graphType.Name);

            if (isAScalarAlready)
            {
                throw new GraphTypeDeclarationException(
                          $"A scalar named '{graphType.Name}' already exists in this graphql instance.");
            }

            isAScalarAlready = this.IsScalar(graphType.ObjectType);
            if (isAScalarAlready)
            {
                var scalar = this.RetrieveScalar(graphType.ObjectType);
                throw new GraphTypeDeclarationException(
                          $"The scalar's primary object type of '{graphType.ObjectType.FriendlyName()}' is " +
                          $"already reserved by the scalar '{scalar.Name}'. Scalar object types must be unique.");
            }

            foreach (var type in graphType.OtherKnownTypes)
            {
                isAScalarAlready = this.IsScalar(type);
                if (isAScalarAlready)
                {
                    var scalar = this.RetrieveScalar(type);
                    throw new GraphTypeDeclarationException(
                              $"The scalar's other known type of '{type.FriendlyName()}' is " +
                              $"already reserved by the scalar '{scalar.Name}'. Scalar object types must be unique.");
                }
            }

            this.AddScalar(graphType);
        }
Example #28
0
        /// <summary>
        /// When overridden in a child class this method builds out the template according to its own individual requirements.
        /// </summary>
        protected override void ParseTemplateDefinition()
        {
            _fieldDeclaration = this.SingleAttributeOfTypeOrDefault <GraphFieldAttribute>();

            // ------------------------------------
            // Common Metadata
            // ------------------------------------
            this.Route       = this.GenerateFieldPath();
            this.Mode        = _fieldDeclaration?.ExecutionMode ?? FieldResolutionMode.PerSourceItem;
            this.Complexity  = _fieldDeclaration?.Complexity;
            this.Description = this.SingleAttributeOfTypeOrDefault <DescriptionAttribute>()?.Description;
            var depreciated = this.SingleAttributeOfTypeOrDefault <DeprecatedAttribute>();

            if (depreciated != null)
            {
                this.IsDeprecated      = true;
                this.DeprecationReason = depreciated.Reason?.Trim();
            }

            var objectType     = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType);
            var typeExpression = GraphValidation.GenerateTypeExpression(this.DeclaredReturnType, this);

            typeExpression = typeExpression.CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind));

            // ------------------------------------
            // Gather Possible Types and/or union definition
            // ------------------------------------
            this.BuildUnionProxyInstance();
            if (this.UnionProxy != null)
            {
                this.PossibleTypes = new List <Type>(this.UnionProxy.Types);
            }
            else
            {
                this.PossibleTypes = new List <Type>();

                // the possible types attribte is optional but expects taht the concrete types are added
                // to the schema else where lest a runtime exception occurs of a missing graph type.
                var typesAttrib = this.SingleAttributeOfTypeOrDefault <PossibleTypesAttribute>();
                if (typesAttrib != null)
                {
                    foreach (var type in typesAttrib.PossibleTypes)
                    {
                        this.PossibleTypes.Add(type);
                    }
                }

                // add any types decalred on the primary field declaration
                if (_fieldDeclaration != null)
                {
                    foreach (var type in _fieldDeclaration.Types)
                    {
                        var strippedType = GraphValidation.EliminateWrappersFromCoreType(type);
                        if (strippedType != null)
                        {
                            this.PossibleTypes.Add(strippedType);
                        }
                    }
                }

                if (objectType != null && !Validation.IsCastable <IGraphActionResult>(objectType) && GraphValidation.IsValidGraphType(objectType))
                {
                    this.PossibleTypes.Insert(0, objectType);
                }
            }

            this.PossibleTypes = this.PossibleTypes.Distinct().ToList();

            // ------------------------------------
            // Adjust the action result to the actual return type this field returns
            // ------------------------------------
            if (Validation.IsCastable <IGraphActionResult>(objectType))
            {
                if (this.UnionProxy != null)
                {
                    // if a union was decalred preserve whatever modifer elements
                    // were decalred but alter the return type to object (a known common element among all members of the union)
                    objectType = typeof(object);
                }
                else if (_fieldDeclaration != null && _fieldDeclaration.Types.Count > 0)
                {
                    objectType     = _fieldDeclaration.Types[0];
                    typeExpression = GraphValidation.GenerateTypeExpression(objectType, this)
                                     .CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind));
                    objectType = GraphValidation.EliminateWrappersFromCoreType(objectType);
                }
                else if (this.PossibleTypes.Count > 0)
                {
                    objectType     = this.PossibleTypes[0];
                    typeExpression = GraphValidation.GenerateTypeExpression(objectType, this)
                                     .CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind));
                    objectType = GraphValidation.EliminateWrappersFromCoreType(objectType);
                }
                else
                {
                    objectType = typeof(object);
                }
            }

            this.ObjectType = objectType;

            if (this.UnionProxy != null)
            {
                this.TypeExpression = typeExpression.CloneTo(this.UnionProxy.Name);
            }
            else
            {
                this.TypeExpression = typeExpression;
            }

            // ------------------------------------
            // Async Requirements
            // ------------------------------------
            this.IsAsyncField = Validation.IsCastable <Task>(this.DeclaredReturnType);

            // ------------------------------------
            // Security Policies
            // ------------------------------------
            _securityPolicies = FieldSecurityGroup.FromAttributeCollection(this.AttributeProvider);
        }
Example #29
0
        /// <summary>
        /// Validates the completed field context to ensure it is "correct" against the specification before finalizing its reslts.
        /// </summary>
        /// <param name="context">The context containing the resolved field.</param>
        /// <returns><c>true</c> if the node is valid, <c>false</c> otherwise.</returns>
        public override bool Execute(FieldValidationContext context)
        {
            var typeExpression = context.TypeExpression;

            if (typeExpression.IsRequired && context.ResultData == null)
            {
                // 6.4.3 section 1c
                this.ValidationError(
                    context,
                    $"Field '{context.FieldPath}' expected a non-null result but received {{null}}.");

                context.DataItem.InvalidateResult();
            }

            // 6.4.3 section 2
            if (context.ResultData == null)
            {
                return(true);
            }

            // 6.4.3 section 3, ensure an IEnumerable for a type expression that is a list
            if (typeExpression.IsListOfItems && !GraphValidation.IsValidListType(context.ResultData.GetType()))
            {
                this.ValidationError(
                    context,
                    $"Field '{context.FieldPath}' was expected to return a list of items but instead returned a single item.");

                context.DataItem.InvalidateResult();
                return(true);
            }

            var graphType = context.Schema?.KnownTypes.FindGraphType(typeExpression?.TypeName);

            if (graphType == null)
            {
                this.ValidationError(
                    context,
                    $"The graph type for field '{context.FieldPath}' ({typeExpression?.TypeName}) does not exist on the target schema. The field" +
                    $"cannot be properly evaluated.");

                context.DataItem.InvalidateResult();
            }
            else if (!typeExpression.Matches(context.ResultData, graphType.ValidateObject))
            {
                // generate a valid, properly cased type expression reference for the data that was provided
                var actualExpression = GraphValidation.GenerateTypeExpression(context.ResultData.GetType());
                var coreType         = GraphValidation.EliminateWrappersFromCoreType(context.ResultData.GetType());
                var actualType       = context.Schema.KnownTypes.FindGraphType(coreType);
                if (actualType != null)
                {
                    actualExpression = actualExpression.CloneTo(actualType.Name);
                }

                // 6.4.3  section 4 & 5
                this.ValidationError(
                    context,
                    $"The resolved value for field '{context.FieldPath}' does not match the required type expression. " +
                    $"Expected {typeExpression} but got {actualExpression}.");

                context.DataItem.InvalidateResult();
            }

            return(true);
        }
        public void IsValidListType(Type inputType, bool isValidListType)
        {
            var result = GraphValidation.IsValidListType(inputType);

            Assert.AreEqual(isValidListType, result);
        }