diff --git a/src/HotChocolate/Core/src/Types/Configuration/Validation/InputObjectTypeValidationRule.cs b/src/HotChocolate/Core/src/Types/Configuration/Validation/InputObjectTypeValidationRule.cs index a78187b0d87..3e5a13aca0f 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/Validation/InputObjectTypeValidationRule.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/Validation/InputObjectTypeValidationRule.cs @@ -2,6 +2,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using HotChocolate.Language; using HotChocolate.Types; using static HotChocolate.Configuration.Validation.TypeValidationHelper; using static HotChocolate.Utilities.ErrorHelper; @@ -15,18 +18,104 @@ public void Validate( IReadOnlySchemaOptions options, ICollection errors) { - if (options.StrictValidation) + if (!options.StrictValidation) { - List? names = null; + return; + } + + List? names = null; + CycleValidationContext cycleValidationContext = new() + { + Visited = new(), + CycleStartIndex = new(), + Errors = errors, + FieldPath = new(), + }; + + foreach (var type in typeSystemObjects) + { + if (type is not InputObjectType inputType) + { + continue; + } + + EnsureTypeHasFields(inputType, errors); + EnsureFieldNamesAreValid(inputType, errors); + EnsureOneOfFieldsAreValid(inputType, errors, ref names); + EnsureFieldDeprecationIsValid(inputType, errors); + TryReachCycleRecursively(cycleValidationContext, inputType); + + cycleValidationContext.CycleStartIndex.Clear(); + } + } + + private struct CycleValidationContext + { + public HashSet Visited { get; set; } + public Dictionary CycleStartIndex { get; set; } + public ICollection Errors { get; set; } + public List FieldPath { get; set; } + } + + // https://github.com/IvanGoncharov/graphql-js/blob/408bcda9c88df85e039f5d072011b1cb465fe830/src/type/validate.js#L535 + private static void TryReachCycleRecursively( + in CycleValidationContext context, + InputObjectType type) + { + if (!context.Visited.Add(type)) + { + return; + } + + context.CycleStartIndex[type] = context.FieldPath.Count; + + foreach (var field in type.Fields) + { + var unwrappedType = UnwrapCompletelyIfRequired(field.Type); + if (unwrappedType is not InputObjectType inputObjectType) + { + continue; + } + + context.FieldPath.Add(field.Name); + if (context.CycleStartIndex.TryGetValue(inputObjectType, out var cycleIndex)) + { + var cyclePath = context.FieldPath.Skip(cycleIndex); + context.Errors.Add( + InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf(type, cyclePath)); + } + else + { + TryReachCycleRecursively(context, inputObjectType); + } + context.FieldPath.Pop(); + } + + context.CycleStartIndex.Remove(type); + } + + private static IType? UnwrapCompletelyIfRequired(IType type) + { + while (true) + { + if (type.Kind == TypeKind.NonNull) + { + type = ((NonNullType)type).Type; + } + else + { + return null; + } - foreach (var type in typeSystemObjects) + switch (type.Kind) { - if (type is InputObjectType inputType) + case TypeKind.List: { - EnsureTypeHasFields(inputType, errors); - EnsureFieldNamesAreValid(inputType, errors); - EnsureOneOfFieldsAreValid(inputType, errors, ref names); - EnsureFieldDeprecationIsValid(inputType, errors); + return null; + } + default: + { + return type; } } } @@ -37,30 +126,33 @@ private static void EnsureOneOfFieldsAreValid( ICollection errors, ref List? temp) { - if (type.Directives.ContainsDirective(WellKnownDirectives.OneOf)) + if (!type.Directives.ContainsDirective(WellKnownDirectives.OneOf)) { - temp ??= new List(); + return; + } - foreach (var field in type.Fields) - { - if (field.Type.Kind is TypeKind.NonNull || field.DefaultValue is not null) - { - temp.Add(field.Name); - } - } + temp ??= new List(); - if (temp.Count > 0) + foreach (var field in type.Fields) + { + if (field.Type.Kind is TypeKind.NonNull || field.DefaultValue is not null) { - var fieldNames = new string[temp.Count]; + temp.Add(field.Name); + } + } - for (var i = 0; i < temp.Count; i++) - { - fieldNames[i] = temp[i]; - } + if (temp.Count == 0) + { + return; + } - temp.Clear(); - errors.Add(OneofInputObjectMustHaveNullableFieldsWithoutDefaults(type, fieldNames)); - } + var fieldNames = new string[temp.Count]; + for (var i = 0; i < temp.Count; i++) + { + fieldNames[i] = temp[i]; } + + temp.Clear(); + errors.Add(OneofInputObjectMustHaveNullableFieldsWithoutDefaults(type, fieldNames)); } } diff --git a/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs b/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs index d5b30270b98..73b4ddee40c 100644 --- a/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs +++ b/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs @@ -1821,6 +1821,12 @@ internal static string ErrorHelper_OneofInputObjectMustHaveNullableFieldsWithout } } + internal static string ErrorHelper_InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf { + get { + return ResourceManager.GetString("ErrorHelper_InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf", resourceCulture); + } + } + internal static string ErrorHelper_RequiredArgumentCannotBeDeprecated { get { return ResourceManager.GetString("ErrorHelper_RequiredArgumentCannotBeDeprecated", resourceCulture); diff --git a/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx b/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx index 8fee808baa7..6b880f14af3 100644 --- a/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx +++ b/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx @@ -1,1012 +1,1015 @@ - - + + - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The {0}-directive is missing the if-argument. - + The argument type has to be an input-type. - + Argument `{0}` of non-null type `{1}` must not be null. - + The `Boolean` scalar type represents `true` or `false`. - + The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255. - + The specified IComplexTypeFieldBindingBuilder-implementation is not supported. - + The field binding builder is not completed and cannot be added. - + The DataLoader key cannot be null or empty. - + No DataLoader registry was registered with your dependency injection. - + Unable to register a DataLoader with your DataLoader registry. - + The `DateTime` scalar represents an ISO-8601 compliant date time type. - + The `Date` scalar represents an ISO-8601 compliant date type. - + The built-in `Decimal` scalar type. - + The specified member has to be a method or a property. - + Only type system objects are allowed as schema type. - + The specified directive `@{0}` is unique and cannot be added twice. - + The specified directive `@{0}` is not allowed on the current location `{1}`. - + Location adjacent to an argument definition - + A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies. - + Location adjacent to an enum definition. - + Location adjacent to an enum value definition. - + Location adjacent to a field. - + Location adjacent to a field definition. - + Location adjacent to a fragment definition. - + Location adjacent to a fragment spread. - + Location adjacent to an inline fragment. - + Location adjacent to an input object field definition. - + Location adjacent to an input object type definition. - + Location adjacent to an interface definition. - + Location adjacent to a mutation operation. - + Location adjacent to an object type definition. - + Location adjacent to a query operation. - + Location adjacent to a scalar definition. - + Location adjacent to a schema definition. - + Location adjacent to a subscription operation. - + Location adjacent to a union definition. - + Only property expressions are allowed to describe a directive type argument. - + The specified location `{0}` is not supported. - + The `{0}` directive does not declare any location on which it is valid. - + Replace Middleware with `Use`. - + Unable to convert the argument value to the specified type. - + A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor. - + Use `locations`. - + The enum type extension can only be merged with an enum type. - + The enum value `{0}` of the enum type extension is not assignable with the target enum type. - + The enum type `{0}` has no values. - + One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string. - + The inner value of enum value cannot be null or empty. - + Could not parse the native value of input field `{0}`. - + {0} `{1}` has no fields declared. - + Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type. - + The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). - + The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID. - + Unable to set the input field value. - + The input object type extension can only be merged with an input object type. - + The input object type can only parse object value literals. - + The input object `{0}` does not have any fields. - + The input value of type `{0}` must not be null. - + A GraphQL-formatted string representing the default value for this input value. - + Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value. - + The arguments of the interface field {0} from interface {1} and {2} do not match and are implemented by object type {3}. - + Object type {0} does not implement all arguments of field {1} from interface {2}. - + Object type {0} does not implement the field {1} from interface {2}. - + The return type of the interface field {0} from interface {1} and {2} do not match and are implemented by object type {3}. - + The return type of the interface field {0} does not match the field declared by object type {1}. - + The interface type extension can only be merged with an interface type. - + The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. - + The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1. - + The multiplier path scalar represents a valid GraphQL multiplier path string. - + The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types. - + The multiplier path scalar represents a valid GraphQL multiplier path string. - + The field-type must be an output-type. - + The interface base class cannot be used as interface implementation declaration. - + The interface base class cannot be used as interface implementation declaration. - + A field-expression must be a property-expression or a method-call-expression. - + The resolver type {0} cannot be used, a non-abstract type is required. - + A node-resolver-expression must be a method-call-expression. - + An ID-member must be a property-expression or a method-call-expression. - + Schema types cannot be used as resolver types. - + The member expression must specify a property or method that is public and that belongs to the type {0} - + A directive type mustn't be one of the base classes `DirectiveType` or `DirectiveType<T>` but must be a type inheriting from `DirectiveType` or `DirectiveType<T>`. - + The specified IResolverFieldBindingBuilder-implementation is not supported. - + The field binding builder is not completed and cannot be added. - + {0} cannot deserialize the given value. - + {0} cannot parse the given literal of type `{1}`. - + {0} cannot parse the given value of type `{1}`. - + {0} cannot serialize the given value. - + A directive type mustn't be one of the base classes `DirectiveType` or `DirectiveType<T>` but must be a type inheriting from `DirectiveType` or `DirectiveType<T>`. - + A directive type must inherit from `DirectiveType` or `DirectiveType<T>`. - + The schema string cannot be null or empty. - + There is no handler registered that can handle the specified schema binding. - + The schema binding is not valid. - + The given schema has to inherit from TypeSystemObjectBase in order to be initializable. - + The schema builder was unable to identify the query type of the schema. Either specify which type is the query type or set the schema builder to non-strict validation mode. - + A root type must be a class. - + A root type must be an object type. - + Non-generic schema types are not allowed. - + The given schema has to inherit from `Schema` in order to be initializable. - + The error message mustn't be null or empty. - + Access the current type schema of this server. - + Unknown operation type. - + A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. - + A list of all directives supported by this server. - + If this server supports mutation, the type that mutation operations will be rooted at. - + The type that query operations will be rooted at. - + If this server support subscription, the type that subscription operations will be rooted at. - + A list of all types supported by this server. - + The `Short` scalar type represents non-fractional signed whole 16-bit numeric values. Short can represent values between -(2^15) and 2^15 - 1. - + The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. - + The `{0}` cannot be null or empty. - + The configuration delegate mustn't be null. - + Definition mustn't be null. - + The specified type is not a schema type. - + The type structure is invalid. - + The specified type kind is not supported. - + The specified type is not a valid list type. - + The given type is not a {0}. - + Request the type information of a single type. - + Unable to resolve dependencies {1} for type `{0}`. - + The name `{0}` was already registered by another type. - + The kind of the extension does not match the kind of the type `{0}`. - + An enum describing what kind of type a given `__Type` is. - + Indicates this type is an enum. `enumValues` is a valid field. - + Indicates this type is an input object. `inputFields` is a valid field. - + Indicates this type is an interface. `fields` and `possibleTypes` are valid fields. - + Indicates this type is a list. `ofType` is a valid field. - + Indicates this type is a non-null. `ofType` is a valid field. - + Indicates this type is an object. `fields` and `interfaces` are valid fields. - + Indicates this type is a scalar. - + Indicates this type is a union. `possibleTypes` is a valid field. - + The name of the current Object type at runtime. - + Invalid type structure. - + Only type system objects are allowed as dependency. - + The typeName mustn't be null or empty. - + The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types. - + The union type extension can only be merged with an union type. - + Variable `{0}` of type `{1}` must be an input type. - + Variable `{0}` got invalid value. - + The type node kind is not supported. - + Variable `{0}` of type `{1}` must not be null. - + Detected non-null violation in variable `{0}`. - + Variable name mustn't be null or empty. - + The argument `{0}` has no type. Specify the type with `.Argument("{0}", a.Type<MyType>())` to fix this issue. - + The specified type is not an input type. - + The inner type of non-null type must be a nullable type. - + A non null type cannot parse null value literals. - + The object type extension can only be merged with an object type. - + The type definition is null which means that the type was initialized incorrectly. - + The type name was not completed correctly and is still empty. Type names are not allowed to remain empty after name completion was executed. Type: `{0}` - + The description becomes immutable once it was assigned. - + The name becomes immutable once it was assigned. - + A Union type must define one or more unique member types. - + Unable to resolve the specified type reference. - + schemaType must be a schema type. - + Unable to infer or resolve a schema type from the type reference `{0}`. - + Unable to convert type from `{0}` to `{1}` - + The specified interceptor type is not supported. - + Unable to encode data. - + Unable to decode the id string. - + The specified convention type is not supported. - + The `TimeSpan` scalar represents an ISO-8601 compliant duration type. - + The DataLoader `{0}` was not of the requested type `{1}`. - + The DataLoader `{0}` needs to be register with the dependency injection provider. - + Unable to create DataLoader `{0}`. - + The specified type is not an input type. - + The completion context has not been initialized. - + The completion context can only be set once. - + The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`. - + If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to. - + Deferred when true. - + The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`. - + If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to. - + The initial elements that shall be send down to the consumer. - + Streamed when true. - + The root type `{0}` has already been registered. - + The ID field must be a property or a method. - + The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema,such as deprecated fields on a type or deprecated enum values. - + Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by CommonMark). - + Directs the executor to include this field or fragment only when the `if` argument is true. - + Included when true. - + Directs the executor to skip this field or fragment when the `if` argument is true. - + Skipped when true. - + The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions. - + The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types. - + The node interface is implemented by entities that have a global unique identifier. - + Cycle in object graph detected. - + Unknown format. Guid supports the following format chars: {{ `N`, `D`, `B`, `P` }}. https://docs.microsoft.com/en-us/dotnet/api/system.buffers.text.utf8parser.tryparse?view=netcore-3.1#System_Buffers_Text_Utf8Parser_TryParse_System_ReadOnlySpan_System_Byte__System_Guid__System_Int32__System_Char - + The argument name is invalid. - + An Applied Directive is an instances of a directive as applied to a schema element. This type is NOT specified by the graphql specification presently. - + Directive arguments can have names and values. The values are in graphql SDL syntax printed as a string. This type is NOT specified by the graphql specification presently. - + Unable to infer the element type from the current resolver. This often happens if the resolver is not an iterable type like IEnumerable, IQueryable, IList etc. Ensure that you either explicitly specify the element type or that the return type of your resolver is an iterable type. - + The specified type `{0}` does not exist. - + The field `{0}.{1}` has no resolver. - + The non-generic IExecutable interface cannot be used as a type in the schema. - + The specified binding cannot be handled. - + `specifiedByURL` may return a String (in the form of a URL) for custom scalars, otherwise it will return `null`. - + The specified type `{0}` is a GraphQL schema type. AddObjectType<T> is a helper method to register a runtime type as GraphQL object type. Use AddType<T> to register GraphQL schema types. - + The specified type `{0}` is a GraphQL schema type. AddUnionType<T> is a helper method to register a runtime type as GraphQL union type. Use AddType<T> to register GraphQL schema types. - + The specified type `{0}` is a GraphQL schema type. AddEnumType<T> is a helper method to register a runtime type as GraphQL enum type. Use AddType<T> to register GraphQL schema types. - + The specified type `{0}` is a GraphQL schema type. AddInterfaceType<T> is a helper method to register a runtime type as GraphQL interface type. Use AddType<T> to register GraphQL schema types. - + The specified type `{0}` is a GraphQL schema type. AddInputObjectType<T> is a helper method to register a runtime type as GraphQL input object type. Use AddType<T> to register GraphQL schema types. - + The complexity cannot be below one. - + Default multiplier cannot be below two. - + The event message parameter can only be used in a subscription context. - + The public method should already have ensured that we do not have members other than method or property at this point. - + Only methods are allowed. - + The schema builder context is invalid. - + Empty field coordinates are not allowed. - + The specified key `{0}` does not exist on `context.ContextData`. - + The specified context key does not exist. - + The specified key `{0}` does not exist on `context.ScopedContextData`. - + Could not resolve the claims principal. - + Location adjacent to a variable definition. - + The resolver type needs to be a public non-abstract non-static class. - + The resolver type needs to be a class or interface - + Fetches an object given its ID. - + ID of the object. - + Lookup nodes by a list of IDs. - + The list of node IDs. - + The middleware pipeline order for the field `{0}` is invalid. Middleware order is important especially with data pipelines. The correct order of a data pipeline is as follows: UseDbContext -> UsePaging -> UseProjection -> UseFiltering -> UseSorting. You may omit any of these middleware or have other middleware in between but you need to abide by the overall order. Your order is: {1}. - + The type {0} is invalid because the runtime type is a {1}. It is not supported to have type system members as runtime types. - + The max expected field count cannot be smaller than 1. - + The object is not yet ready for this action. - + Edge types that have a non-object node are not supported. - + An edge in a connection. - + A cursor for use in pagination. - + The item at the end of the edge. - + A connection to a list of items. - + Information to aid in pagination. - + A list of edges. - + Identifies the total count of items in the connection. - + Information to aid in pagination. - + A segment of a collection. - + A flattened list of the items. - + A flattened list of the nodes. - + The middleware order is invalid since the service scope is missing. - + The fieldName cannot be null or empty. - + The `@oneOf` directive is used within the type system definition language to indicate: - an Input Object is a Oneof Input Object, or - an Object Type's Field is a Oneof Field. - + The Oneof Input Objects `{0}` require that exactly one field must be supplied and that field must not be `null`. Oneof Input Objects are a special variant of Input Objects where the type system asserts that exactly one of the fields must be set and non-null. - + More than one field of the Oneof Input Object `{0}` is set. Oneof Input Objects are a special variant of Input Objects where the type system asserts that exactly one of the fields must be set and non-null. - + `null` was set to the field `{0}`of the Oneof Input Object `{1}`. Oneof Input Objects are a special variant of Input Objects where the type system asserts that exactly one of the fields must be set and non-null. - + Member is not a method! - + The specified key `{0}` does not exist on `context.ScopedContextData` - + The specified key `{0}` does not exist on `context.LocalContextData` - + The specified key `{0}` does not exist on `context.ContextData` - + The specified type `{0}` does not exist or is not of the specified kind `{1}`. - + The schema types definition is in an invalid state. - + Only properties are allowed for input types. - + A field of an interface can only be inferred from a property or a method. - + The field is already sealed and cannot be mutated. - + Unable to find type(s) {0} - + Unable to resolve type from field `{0}`. - + An object type at this point is guaranteed to have a type definition, but we found none. - + A field argument at this initialization state is guaranteed to have an argument type, but we found none. - + A type with the name `{0}` was not found. - + Field `{0}` was not found on type `{1}`. - + Argument `{0}` was not found on field `{1}.{2}`. - + The coordinate `{0}` is invalid for the type `{1}`. - + Input field `{0}` was not found on type `{1}`. - + Enum value `{0}` was not found on type `{1}`. - + Directive `@{0}` not found. - + Argument `{0}` was not found on directive `@{1}`. - + The type `{0}` does mot expect `{1}`. - + The list result value of {0} must implement IList but is of the type {1}. - + The input object `{1}` must to be of type `{2}` or serialized as `IReadOnlyDictionary<string. object?>` but not as `{0}`. - + The list runtime value of {0} must implement IEnumerable or IList but is of the type {1}. - + The list `{1}` must to be serialized as `{2}` or as `IList` but not as `{0}`. - + The item syntax node for a nested list must be `ListValue` but the parser found `{0}`. - + The input object `{1}` must to be serialized as `{2}` or as `IReadOnlyDictionary<string. object?>` but not as `{0}`. - + The syntax node `{0}` is incompatible with the type `{1}`. - + Cannot accept null for non-nullable input. - + The fields `{0}` do not exist on the type `{1}`. - + The required input field `{0}` is missing. - + The provided type {0} is not a dataloader - + Convention of type {0} in scope {1} could not be created - + There are two conventions registered for {0} in scope {1}. Only one convention is allowed. Use convention extensions if additional configuration is needed. Colliding conventions are {2} and {3} - + The specified id field `{0}` does not exist on `{1}`. - + Unable to resolve type reference `{0}`. - + Unable to create instance of type `{0}`. - + Unable to create a convention instance from {0}. - + Unable to find the subscribe resolver `{2}` defined on {0}.{1}. The subscribe resolver bust be a method that is public, non-static and on the same type as the resolver. (SubscribeAttribute) - + You need to specify the topic type on {0}.{1}. (SubscribeAttribute) - + You need to specify the message type on {0}.{1}. (SubscribeAttribute) - + There is no event message on the context. - + The event message is of the type `{0}` and cannot be casted to `{1}.` - + The {0} type `{1}` has to at least define one field in order to be valid. - + Field names starting with `__` are reserved for the GraphQL specification. - + Argument names starting with `__` are reserved for the GraphQL specification. - + Names starting with `__` are reserved for the GraphQL specification. - + The {0} type must also declare all interfaces declared by implemented interfaces. - + Field `{0}` must return a type which is equal to or a subtype of (covariant) the return type `{1}` of the interface field. - + The field `{0}` must be implemented by {1} type `{2}`. - + The named argument `{0}` on field `{1}` must accept the same type `{2}` (invariant) as that named argument on the interface `{3}`. - + The field `{0}` must only declare additional arguments to an implemented field that are nullable. - + The argument `{0}` of the implemented field `{1}` must be defined. The field `{2}` must include an argument of the same name for every argument defined on the implemented field of the interface type `{3}`. - + Oneof Input Object `{0}` must only have nullable fields without default values. Edit your type and make the field{1} `{2}` nullable and remove any defaults. - + + Cannot reference Input Object `{0}` within itself through a series of non-null fields `{1}`. + + Required argument {0} cannot be deprecated. - + Required input field {0} cannot be deprecated. - + There is no object type implementing interface `{0}`. - + Unable to resolve the interface type. For more details look at the error object. - + The argument `{0}` does not exist on the directive `{1}`. - + The argument `{0}` of directive `{1}` mustn't be null. - + Unable to infer or resolve the type of field {0}.{1}. Try to explicitly provide the type like the following: `descriptor.Field("field").Type<List<StringType>>()`. - + There is no node resolver registered for type `{0}`. - + The node resolver `{0}` must specify exactly one argument. - + The node resolver `{0}` must return an object type. - + The type `{0}` implementing the node interface must expose an id field. - + The field `{0}` does not exist on the type `{1}`. - + Mutation conventions infer the error name from the mutation. In this case the error union was inferred from the mutation `{0}` as `{1}`, but the type initialization encountered another object with the name `{1}`. Either rename the error object or specify a naming exception for this particular mutation. You can do that by using the `UseMutationConventionAttribute` for instance. - + The type `{0}` implements the node interface but does not provide a node resolver for re-fetching. - + The shape of the enum {0} is not known - + Flags need to have at least one selection. Type: {0} - + The value {0} is not known for type {1} - + One of the values of {0} does not have a valid name: {1} - + The directive '{0}' has no argument with the name '{1}'. - + The directive arguments have invalid values: '{0}' at {1}. - + TypeReference kind not supported. - + The maximum number of nodes that can be fetched at once is {0}. This selection tried to fetch {1} nodes that exceeded the maximum allowed amount. - + The specified type `{0}` is expected to be an input type. - + The specified type `{0}` is expected to be an output type. - + The tag name must follow the GraphQL type name rules. - + Tag is not supported on the specified descriptor. - + Adding an error type `{0}` to field `{1}` failed as mutation conventions weren't enabled. - + The following {0}{1} `{2}` {3} declared multiple times on `{4}`. - + The field `{0}` declares the data middleware `{1}` more than once. - + Unexpected schema exception occurred. - + For more details look at the `Errors` property. diff --git a/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs b/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs index 83329255b7c..5b93d3d0cff 100644 --- a/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs +++ b/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs @@ -177,6 +177,18 @@ public static ISchemaError OneofInputObjectMustHaveNullableFieldsWithoutDefaults .SetSpecifiedBy(type.Kind, rfc: 825) .Build(); + public static ISchemaError InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf( + InputObjectType type, + IEnumerable path) + => SchemaErrorBuilder.New() + .SetMessage( + ErrorHelper_InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf, + type.Name, + string.Join(" --> ", path)) + .SetType(type) + .SetSpecifiedBy(type.Kind, rfc: 445) + .Build(); + public static ISchemaError RequiredArgumentCannotBeDeprecated( IComplexOutputType type, IOutputField field, @@ -404,7 +416,7 @@ public static ISchemaError MiddlewareOrderInvalid( .AddSyntaxNode(syntaxNode) .SetExtension(nameof(field), field) .Build(); - + public static ISchemaError DuplicateDataMiddlewareDetected( FieldCoordinate field, ITypeSystemObject type, @@ -503,8 +515,8 @@ public static ISchemaError NoFields( .Build(); public static ISchemaError DuplicateFieldName( - ITypeSystemObject type, - ITypeSystemMember declaringMember, + ITypeSystemObject type, + ITypeSystemMember declaringMember, IReadOnlyCollection duplicateFieldNames) { var field = declaringMember is IType diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/packages.lock.json b/src/HotChocolate/Core/test/Types.Analyzers.Tests/packages.lock.json index 617d7a3e939..eec800d57db 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/packages.lock.json +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/packages.lock.json @@ -1252,7 +1252,7 @@ "HotChocolate.Subscriptions.InMemory": "[0.0.0, )", "HotChocolate.Transport.Sockets": "[0.0.0, )", "HotChocolate.Types.Scalars.Upload": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )" + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )" } }, "hotchocolate.authorization": { @@ -1268,7 +1268,7 @@ "HotChocolate.Execution.Abstractions": "[0.0.0, )", "HotChocolate.Fetching": "[0.0.0, )", "HotChocolate.Types": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )", + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )", "HotChocolate.Validation": "[0.0.0, )", "Microsoft.Extensions.DependencyInjection": "[6.0.0, )", "System.Threading.Channels": "[6.0.0, )" diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Validation/InputObjectTypeValidationRuleTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/InputObjectTypeValidationRuleTests.cs index 521af5ad452..710508e0b65 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Validation/InputObjectTypeValidationRuleTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/InputObjectTypeValidationRuleTests.cs @@ -1,5 +1,4 @@ using HotChocolate.Configuration.Validation; -using Xunit; namespace HotChocolate.Types.Validation; @@ -8,106 +7,180 @@ public class InputObjectTypeValidationRuleTests : TypeValidationTestBase [Fact] public void RejectInputTypeWithoutFields() { - ExpectError(@" + ExpectError(""" type Query { stub: String } input Foo {} - "); + """); } [Fact] public void AcceptInputTypeWithFields() { - ExpectValid(@" + ExpectValid(""" type Query { stub: String } input Foo { nullable: String nonNullable: String! - defaultNullable: String = ""Foo"" - defaultNonNullable: String! = ""Foo"" + defaultNullable: String = "Foo" + defaultNonNullable: String! = "Foo" } - "); + """); } [Fact] public void AcceptInputTypeWithFieldsAndDirectives() { - ExpectValid(@" + ExpectValid(""" type Query { stub: String } input Foo @inputObject { nullable: String @inputFieldDefinition nonNullable: String! @inputFieldDefinition - defaultNullable: String = ""Foo"" @inputFieldDefinition - defaultNonNullable: String! = ""Foo"" @inputFieldDefinition + defaultNullable: String = "Foo" @inputFieldDefinition + defaultNonNullable: String! = "Foo" @inputFieldDefinition } directive @inputFieldDefinition on INPUT_FIELD_DEFINITION directive @inputObject on INPUT_OBJECT - "); + """); } [Fact] public void RejectFieldsWithInvalidName() { - ExpectError(@" + ExpectError(""" type Query { stub: String } input Foo { __badField: String } - "); + """); } [Fact] public void AcceptOneOfWithNullableFields() { - ExpectValid(@" + ExpectValid(""" type Query { stub: String } input Foo @oneOf { first: String second: Int } - "); + """); } [Fact] public void RejectOneOfWithNullableFields() { - ExpectError(@" + ExpectError(""" type Query { stub: String } input Foo @oneOf { first: String! second: Int! } - "); + """); } [Fact] public void AcceptNonRequiredInputThatIsDeprecated() { - ExpectValid(@" + ExpectValid(""" type Query { stub: String } input Foo { field: Int @deprecated } - "); + """); } [Fact] public void RejectRequiredFieldThatIsDeprecated() { - ExpectError(@" + ExpectError(""" type Query { stub: String } input Foo { field: Int! @deprecated } - "); + """); + } + + // https://github.com/graphql/graphql-js/pull/1359/files + [Fact] + public void AcceptsBreakableCircularReferences() + { + ExpectValid(""" + type Query { + field(arg: SomeInputObject): String + } + input SomeInputObject { + self: SomeInputObject + arrayOfSelf: [SomeInputObject] + nonNullArrayOfSelf: [SomeInputObject]! + nonNullArrayOfNonNullSelf: [SomeInputObject!]! + intermediateSelf: AnotherInputObject + } + input AnotherInputObject { + parent: SomeInputObject + } + """); + } + + [Fact] + public void RejectsNonBreakableDirectCircularReference() + { + ExpectError(""" + type Query { + field(arg: SomeInputObject): String + } + input SomeInputObject { + nonNullSelf: SomeInputObject! + } + """); + } + + [Fact] + public void RejectsCircularReferenceThroughOtherType() + { + ExpectError(""" + type Query { + field(arg: SomeInputObject): String + } + input SomeInputObject { + startLoop: AnotherInputObject! + } + input AnotherInputObject { + nextInLoop: YetAnotherInputObject! + } + input YetAnotherInputObject { + closeLoop: SomeInputObject! + } + """); + } + + [Fact] + public void RejectsMultipleCircularReferences() + { + ExpectError(""" + type Query { + field(arg: SomeInputObject): String + } + input SomeInputObject { + startLoop: AnotherInputObject! + } + input AnotherInputObject { + closeLoop: SomeInputObject! + startSecondLoop: YetAnotherInputObject! + } + input YetAnotherInputObject { + closeSecondLoop: AnotherInputObject! + nonNullSelf: YetAnotherInputObject! + } + """); } } diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsCircularReferenceThroughOtherType.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsCircularReferenceThroughOtherType.snap new file mode 100644 index 00000000000..94f46c531d0 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsCircularReferenceThroughOtherType.snap @@ -0,0 +1,9 @@ +{ + "message": "Cannot reference Input Object `YetAnotherInputObject` within itself through a series of non-null fields `startLoop --> nextInLoop --> closeLoop`.", + "type": "YetAnotherInputObject", + "extensions": { + "rfc": "https://github.com/graphql/graphql-spec/pull/445", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation" + } +} + diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsMultipleCircularReferences.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsMultipleCircularReferences.snap new file mode 100644 index 00000000000..13954a95813 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsMultipleCircularReferences.snap @@ -0,0 +1,27 @@ +{ + "message": "Cannot reference Input Object `AnotherInputObject` within itself through a series of non-null fields `startLoop --> closeLoop`.", + "type": "AnotherInputObject", + "extensions": { + "rfc": "https://github.com/graphql/graphql-spec/pull/445", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation" + } +} + +{ + "message": "Cannot reference Input Object `YetAnotherInputObject` within itself through a series of non-null fields `startSecondLoop --> closeSecondLoop`.", + "type": "YetAnotherInputObject", + "extensions": { + "rfc": "https://github.com/graphql/graphql-spec/pull/445", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation" + } +} + +{ + "message": "Cannot reference Input Object `YetAnotherInputObject` within itself through a series of non-null fields `nonNullSelf`.", + "type": "YetAnotherInputObject", + "extensions": { + "rfc": "https://github.com/graphql/graphql-spec/pull/445", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation" + } +} + diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsNonBreakableDirectCircularReference.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsNonBreakableDirectCircularReference.snap new file mode 100644 index 00000000000..4f3b4433810 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Validation/__snapshots__/InputObjectTypeValidationRuleTests.RejectsNonBreakableDirectCircularReference.snap @@ -0,0 +1,9 @@ +{ + "message": "Cannot reference Input Object `SomeInputObject` within itself through a series of non-null fields `nonNullSelf`.", + "type": "SomeInputObject", + "extensions": { + "rfc": "https://github.com/graphql/graphql-spec/pull/445", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation" + } +} + diff --git a/src/HotChocolate/Fusion/src/CommandLine/packages.lock.json b/src/HotChocolate/Fusion/src/CommandLine/packages.lock.json index 4e5750e56e3..0b7eeeaf40c 100644 --- a/src/HotChocolate/Fusion/src/CommandLine/packages.lock.json +++ b/src/HotChocolate/Fusion/src/CommandLine/packages.lock.json @@ -225,7 +225,7 @@ "HotChocolate.Subscriptions.InMemory": "[0.0.0, )", "HotChocolate.Transport.Sockets": "[0.0.0, )", "HotChocolate.Types.Scalars.Upload": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )" + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )" } }, "hotchocolate.authorization": { @@ -241,7 +241,7 @@ "HotChocolate.Execution.Abstractions": "[0.0.0, )", "HotChocolate.Fetching": "[0.0.0, )", "HotChocolate.Types": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )", + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )", "HotChocolate.Validation": "[0.0.0, )", "Microsoft.Extensions.DependencyInjection": "[7.0.0, )", "System.Threading.Channels": "[7.0.0, )" @@ -726,7 +726,7 @@ "HotChocolate.Subscriptions.InMemory": "[0.0.0, )", "HotChocolate.Transport.Sockets": "[0.0.0, )", "HotChocolate.Types.Scalars.Upload": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )" + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )" } }, "hotchocolate.authorization": { @@ -742,7 +742,7 @@ "HotChocolate.Execution.Abstractions": "[0.0.0, )", "HotChocolate.Fetching": "[0.0.0, )", "HotChocolate.Types": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )", + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )", "HotChocolate.Validation": "[0.0.0, )", "Microsoft.Extensions.DependencyInjection": "[8.0.0, )", "System.Threading.Channels": "[8.0.0, )" diff --git a/src/HotChocolate/Fusion/test/Shared/packages.lock.json b/src/HotChocolate/Fusion/test/Shared/packages.lock.json index 88b2e4f15c8..201cdb518a0 100644 --- a/src/HotChocolate/Fusion/test/Shared/packages.lock.json +++ b/src/HotChocolate/Fusion/test/Shared/packages.lock.json @@ -1270,7 +1270,7 @@ "HotChocolate.Subscriptions.InMemory": "[0.0.0, )", "HotChocolate.Transport.Sockets": "[0.0.0, )", "HotChocolate.Types.Scalars.Upload": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )" + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )" } }, "hotchocolate.aspnetcore.tests.utilities": { @@ -1302,7 +1302,7 @@ "HotChocolate.Execution.Abstractions": "[0.0.0, )", "HotChocolate.Fetching": "[0.0.0, )", "HotChocolate.Types": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )", + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )", "HotChocolate.Validation": "[0.0.0, )", "Microsoft.Extensions.DependencyInjection": "[7.0.0, )", "System.Threading.Channels": "[7.0.0, )" diff --git a/src/StrawberryShake/Tooling/src/dotnet-graphql/packages.lock.json b/src/StrawberryShake/Tooling/src/dotnet-graphql/packages.lock.json index 44e4a60bb69..39a1712a7ae 100644 --- a/src/StrawberryShake/Tooling/src/dotnet-graphql/packages.lock.json +++ b/src/StrawberryShake/Tooling/src/dotnet-graphql/packages.lock.json @@ -1242,7 +1242,7 @@ "HotChocolate.Execution.Abstractions": "[0.0.0, )", "HotChocolate.Fetching": "[0.0.0, )", "HotChocolate.Types": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )", + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )", "HotChocolate.Validation": "[0.0.0, )", "Microsoft.Extensions.DependencyInjection": "[6.0.0, )", "System.Threading.Channels": "[6.0.0, )" @@ -2628,7 +2628,7 @@ "HotChocolate.Execution.Abstractions": "[0.0.0, )", "HotChocolate.Fetching": "[0.0.0, )", "HotChocolate.Types": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )", + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )", "HotChocolate.Validation": "[0.0.0, )", "Microsoft.Extensions.DependencyInjection": "[7.0.0, )", "System.Threading.Channels": "[7.0.0, )" @@ -4014,7 +4014,7 @@ "HotChocolate.Execution.Abstractions": "[0.0.0, )", "HotChocolate.Fetching": "[0.0.0, )", "HotChocolate.Types": "[0.0.0, )", - "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )", + "HotChocolate.Utilities.DependencyInjection": "[14.0.0-preview.build.0, )", "HotChocolate.Validation": "[0.0.0, )", "Microsoft.Extensions.DependencyInjection": "[8.0.0, )", "System.Threading.Channels": "[8.0.0, )"