Skip to content

Aligned the OneOf implementation with the specification #8352

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ BCPROCKS
bfnrt
blazor
blazorwasm
Brontie
Browsable
BSON
buildtransitive
Expand Down Expand Up @@ -109,7 +110,6 @@ NSwag
ntouches
nwithin
oncall
oneof
OpenIddict
opensgid
OPTOUT
Expand Down
8 changes: 1 addition & 7 deletions src/HotChocolate/Caching/test/Caching.Tests/SchemaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,7 @@ enum CacheControlScope {
"The `@cacheControl` directive may be provided for individual fields or entire object, interface or union types to provide caching hints to the executor."
directive @cacheControl("The maximum amount of time this field's cached value is valid, in seconds." maxAge: Int "The maximum amount of time this field's cached value is valid in shared caches like CDNs, in seconds." sharedMaxAge: Int "If `true`, the field inherits the `maxAge` of its parent field." inheritMaxAge: Boolean "If `PRIVATE`, the field's value is specific to a single user. The default value is `PUBLIC`, which means the field's value is not tied to a single user." scope: CacheControlScope "The Vary HTTP response header describes the parts of the request message aside from the method and URL that influenced the content of the response it occurs in. Most often, this is used to create a cache key when content negotiation is in use." vary: [String]) on OBJECT | FIELD_DEFINITION | INTERFACE | UNION

"""
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` directive is used within the type system definition language to indicate an Input Object is a OneOf Input Object."
directive @oneOf on INPUT_OBJECT

"""
Expand Down
468 changes: 334 additions & 134 deletions src/HotChocolate/Core/src/Execution/Properties/Resources.Designer.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@
<value>The type {0} was already added.</value>
</data>
<data name="ThrowHelper_OneOfFieldMustBeNonNull" xml:space="preserve">
<value>Value for oneof field {0} must be non-null.</value>
<value>Value for OneOf field {0} must be non-null.</value>
</data>
<data name="ThrowHelper_Operation_NoSelectionSet" xml:space="preserve">
<value>The specified selection does not have a selection set.</value>
Expand Down
3 changes: 2 additions & 1 deletion src/HotChocolate/Core/src/Types.Shared/BuiltInTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public static class BuiltInTypes
WellKnownDirectives.Deprecated,
WellKnownDirectives.Defer,
WellKnownDirectives.Stream,
WellKnownDirectives.SpecifiedBy
WellKnownDirectives.SpecifiedBy,
WellKnownDirectives.OneOf
];

public static bool IsBuiltInType(string name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ internal static class WellKnownDirectives
public const string Deprecated = "deprecated";
public const string SpecifiedBy = "specifiedBy";
public const string DeprecationReasonArgument = "reason";
public const string OneOf = "oneOf";
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 6 additions & 10 deletions src/HotChocolate/Core/src/Types/Properties/TypeResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -700,20 +700,16 @@ Type: `{0}`</value>
<value>The fieldName cannot be null or empty.</value>
</data>
<data name="OneOfDirectiveType_Description" xml:space="preserve">
<value>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.</value>
<value>The `@oneOf` directive is used within the type system definition language to indicate an Input Object is a OneOf Input Object.</value>
</data>
<data name="ThrowHelper_OneOfNoFieldSet" xml:space="preserve">
<value>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.</value>
<value>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.</value>
</data>
<data name="ThrowHelper_OneOfMoreThanOneFieldSet" xml:space="preserve">
<value>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.</value>
<value>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.</value>
</data>
<data name="ThrowHelper_OneOfFieldIsNull" xml:space="preserve">
<value>`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.</value>
<value>`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.</value>
</data>
<data name="ReflectionUtils_ExtractMethod_MethodExpected" xml:space="preserve">
<value>Member is not a method!</value>
Expand Down Expand Up @@ -880,8 +876,8 @@ Type: `{0}`</value>
<data name="ErrorHelper_ArgumentNotImplemented" xml:space="preserve">
<value>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}`.</value>
</data>
<data name="ErrorHelper_OneofInputObjectMustHaveNullableFieldsWithoutDefaults" xml:space="preserve">
<value>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.</value>
<data name="ErrorHelper_OneOfInputObjectMustHaveNullableFieldsWithoutDefaults" xml:space="preserve">
<value>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.</value>
</data>
<data name="ErrorHelper_InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf" xml:space="preserve">
<value>Cannot reference Input Object `{0}` within itself through a series of non-null fields `{1}`.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace HotChocolate.Types;

/// <summary>
/// The `@oneOf` directive is used within the type system definition language
/// to indicate an Input Object is a Oneof Input Object.
/// to indicate an Input Object is a OneOf Input Object.
///
/// <code>
/// input UserUniqueCondition @oneOf {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace HotChocolate.Types;

/// <summary>
/// The `@oneOf` directive is used within the type system definition language
/// to indicate an Input Object is a Oneof Input Object.
/// to indicate an Input Object is a OneOf Input Object.
///
/// <code>
/// input UserUniqueCondition @oneOf {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static IInputObjectTypeDescriptor<T> Ignore<T>(
}

/// <summary>
/// Defines an input object type as a oneof input object type
/// Defines an input object type as a OneOf input object type
/// where only ever one field can hold a value.
/// </summary>
/// <param name="descriptor">
Expand All @@ -60,7 +60,7 @@ public static IInputObjectTypeDescriptor OneOf(this IInputObjectTypeDescriptor d
}

/// <summary>
/// Defines an input object type as a oneof input object type
/// Defines an input object type as a OneOf input object type
/// where only ever one field can hold a value.
/// </summary>
/// <param name="descriptor">
Expand Down
8 changes: 4 additions & 4 deletions src/HotChocolate/Core/src/Types/Types/Introspection/__Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ protected override ObjectTypeConfiguration CreateConfiguration(ITypeDiscoveryCon

if (context.DescriptorContext.Options.EnableOneOf)
{
def.Fields.Add(new(Names.OneOf,
def.Fields.Add(new(Names.IsOneOf,
type: booleanType,
pureResolver: Resolvers.OneOf));
pureResolver: Resolvers.IsOneOf));
}

if (context.DescriptorContext.Options.EnableDirectiveIntrospection)
Expand Down Expand Up @@ -160,7 +160,7 @@ public static object Kind(IResolverContext context)
_ => null
};

public static object? OneOf(IResolverContext context)
public static object? IsOneOf(IResolverContext context)
=> context.Parent<IType>() is IInputObjectTypeDefinition iot
? iot.Directives.ContainsName(DirectiveNames.OneOf.Name)
: null;
Expand Down Expand Up @@ -191,7 +191,7 @@ public static class Names
public const string EnumValues = "enumValues";
public const string InputFields = "inputFields";
public const string OfType = "ofType";
public const string OneOf = "oneOf";
public const string IsOneOf = "isOneOf";
public const string SpecifiedByUrl = "specifiedByURL";
public const string IncludeDeprecated = "includeDeprecated";
public const string AppliedDirectives = "appliedDirectives";
Expand Down
2 changes: 1 addition & 1 deletion src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public static ISchemaError OneOfInputObjectMustHaveNullableFieldsWithoutDefaults
string[] fieldNames)
=> SchemaErrorBuilder.New()
.SetMessage(
ErrorHelper_OneofInputObjectMustHaveNullableFieldsWithoutDefaults,
ErrorHelper_OneOfInputObjectMustHaveNullableFieldsWithoutDefaults,
type.Name,
fieldNames.Length is 1 ? string.Empty : "s",
string.Join(", ", fieldNames))
Expand Down
4 changes: 2 additions & 2 deletions src/HotChocolate/Core/src/Validation/ErrorHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ public static IError OneOfMustHaveExactlyOneField(
.AddLocation(node)
.SetPath(context.CreateErrorPath())
.SetExtension(nameof(type), type.Name)
.SpecifiedBy("sec-OneOf-Input-Objects-Have-Exactly-One-Field", rfc: 825)
.SpecifiedBy("sec-All-Variable-Usages-Are-Allowed", rfc: 825)
.Build();

public static IError OneOfVariablesMustBeNonNull(
Expand All @@ -666,7 +666,7 @@ public static IError OneOfVariablesMustBeNonNull(
.AddLocation(node)
.SetPath(context.CreateErrorPath())
.SetFieldCoordinate(fieldCoordinate)
.SpecifiedBy("sec-Oneof–Input-Objects-Have-Exactly-One-Field", rfc: 825)
.SpecifiedBy("sec-All-Variable-Usages-Are-Allowed", rfc: 825)
.Build();

public static IError SkipAndIncludeNotAllowedOnSubscriptionRootField(
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,10 @@
<value>Introspection is not allowed for the current request.</value>
</data>
<data name="ErrorHelper_OneOfMustHaveExactlyOneField" xml:space="preserve">
<value>The Oneof Input Object `{0}` requires that exactly one field must be supplied and that field must not be `null`.</value>
<value>The OneOf Input Object `{0}` requires that exactly one field must be supplied and that field must not be `null`.</value>
</data>
<data name="ErrorHelper_OneOfVariablesMustBeNonNull" xml:space="preserve">
<value>The variable `${0}` assigned to the field `{1}` of the Oneof Input Object `{2}` must be non-null.</value>
<value>The variable `${0}` assigned to the field `{1}` of the OneOf Input Object `{2}` must be non-null.</value>
</data>
<data name="ErrorHelper_SkipAndIncludeNotAllowedOnSubscriptionRootField" xml:space="preserve">
<value>The skip and include directives are not allowed to be used on root fields of the subscription type.</value>
Expand Down
2 changes: 1 addition & 1 deletion src/HotChocolate/Core/src/Validation/Rules/ValueVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ namespace HotChocolate.Validation.Rules;
///
/// AND
///
/// Oneof Input Objects require that exactly one field must be supplied and that
/// OneOf Input Objects require that exactly one field must be supplied and that
/// field must not be {null}.
///
/// DRAFT: https://github.com/graphql/graphql-spec/pull/825
Expand Down
25 changes: 22 additions & 3 deletions src/HotChocolate/Core/src/Validation/Rules/VariableVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ protected override ISyntaxVisitorAction Enter(
if (context.Variables.TryGetValue(
node.Name.Value,
out var variableDefinition)
&& !IsVariableUsageAllowed(variableDefinition, context.Types.Peek(), defaultValue))
&& !IsVariableUsageAllowed(variableDefinition, context, defaultValue))
{
context.ReportError(context.VariableIsNotCompatible(node, variableDefinition));
}
Expand Down Expand Up @@ -281,10 +281,12 @@ protected override ISyntaxVisitorAction Leave(
// http://facebook.github.io/graphql/June2018/#IsVariableUsageAllowed()
private bool IsVariableUsageAllowed(
VariableDefinitionNode variableDefinition,
IType locationType,
DocumentValidatorContext context,
IValueNode? locationDefault)
{
if (locationType.IsNonNullType()
var locationType = context.Types.Peek();

if (IsNonNullPosition(locationType, context)
&& !variableDefinition.Type.IsNonNullType())
{
if (variableDefinition.DefaultValue.IsNull()
Expand All @@ -303,6 +305,23 @@ private bool IsVariableUsageAllowed(
locationType);
}

private static bool IsNonNullPosition(IType locationType, DocumentValidatorContext context)
{
if (locationType.IsNonNullType())
{
return true;
}

if (context.Path.Peek() is ObjectFieldNode
&& context.Types[^2].NullableType() is IInputObjectTypeDefinition inputObjectType
&& inputObjectType.Directives.ContainsName(DirectiveNames.OneOf.Name))
{
return true;
}

return false;
}

// http://facebook.github.io/graphql/June2018/#AreTypesCompatible()
private bool AreTypesCompatible(
ITypeNode variableType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5818,7 +5818,7 @@
"deprecationReason": null
},
{
"name": "oneOf",
"name": "isOneOf",
"description": null,
"args": [],
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@
"deprecationReason": null
},
{
"name": "oneOf",
"name": "isOneOf",
"description": null,
"args": [],
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@
"deprecationReason": null
},
{
"name": "oneOf",
"name": "isOneOf",
"description": null,
"args": [],
"type": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@
"deprecationReason": null
},
{
"name": "oneOf",
"name": "isOneOf",
"description": null,
"args": [],
"type": {
Expand Down
Loading