/// <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> /// 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); }
/// <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); }