/// <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)));
        }
Example #2
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>
 /// 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 #4
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 void ValidateOrThrow()
 {
     GraphValidation.EnsureGraphNameOrThrow(this.InternalFullName, this.Name);
 }