Skip to content
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
7 changes: 7 additions & 0 deletions execution/engine/engine_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
8 changes: 8 additions & 0 deletions execution/engine/testdata/full_introspection.json
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,14 @@
"defaultValue": null
}
]
},
{
"name": "oneOf",
"description": "The @oneOf built-in directive marks an input object as a OneOf Input Object.\nExactly one field must be provided and its value must be non-null at runtime.\nAll fields defined within a @oneOf input must be nullable in the schema.",
"locations": [
"INPUT_OBJECT"
],
"args": []
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,14 @@
"defaultValue": null
}
]
},
{
"name": "oneOf",
"description": "The @oneOf built-in directive marks an input object as a OneOf Input Object.\nExactly one field must be provided and its value must be non-null at runtime.\nAll fields defined within a @oneOf input must be nullable in the schema.",
"locations": [
"INPUT_OBJECT"
],
"args": []
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,15 @@
"defaultValue": null
}
]
},
{
"__typename": "__Directive",
"name": "oneOf",
"description": "The @oneOf built-in directive marks an input object as a OneOf Input Object.\nExactly one field must be provided and its value must be non-null at runtime.\nAll fields defined within a @oneOf input must be nullable in the schema.",
"locations": [
"INPUT_OBJECT"
],
"args": []
}
]
}
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/baseschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ directive @deprecated(

directive @specifiedBy(url: String!) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/fixtures/complete.golden
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/fixtures/custom_query_name.golden
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/fixtures/mutation_only.golden
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/fixtures/schema_missing.golden
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/fixtures/simple.golden
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/fixtures/subscription_only.golden
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
7 changes: 7 additions & 0 deletions v2/pkg/asttransform/fixtures/subscription_renamed.golden
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ directive @specifiedBy(
url: String!
) on SCALAR

"""
The @oneOf built-in directive marks an input object as a OneOf Input Object.
Exactly one field must be provided and its value must be non-null at runtime.
All fields defined within a @oneOf input must be nullable in the schema.
"""
directive @oneOf on INPUT_OBJECT

"""
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
Expand Down
57 changes: 57 additions & 0 deletions v2/pkg/astvalidation/operation_rule_values.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,11 @@ func (v *valuesVisitor) valueSatisfiesInputObjectTypeDefinition(value ast.Value,
return false
}

// Validate @oneOf constraint if present
if v.objectValueViolatesOneOf(value, inputObjectTypeDefinition) {
return false
}

return true
}

Expand Down Expand Up @@ -464,6 +469,58 @@ func (v *valuesVisitor) objectValueHasDuplicateFields(objectValue int) bool {
return hasDuplicates
}

// objectValueViolatesOneOf checks if an input object value violates the @oneOf directive constraint.
func (v *valuesVisitor) objectValueViolatesOneOf(objectValue ast.Value, defRef int) bool {
def := v.definition.InputObjectTypeDefinitions[defRef]
// Check if the input object type has @oneOf directive
if !def.HasDirectives {
return false
}
hasOneOfDirective := def.Directives.HasDirectiveByName(v.definition, "oneOf")
if !hasOneOfDirective {
return false
}

fieldRefs := v.operation.ObjectValues[objectValue.Ref].Refs
if len(fieldRefs) != 1 {
objName := v.definition.InputObjectTypeDefinitionNameBytes(defRef)
v.Report.AddExternalError(operationreport.ErrOneOfInputObjectFieldCount(objName, len(fieldRefs), objectValue.Position))
return true
}

for _, fieldRef := range fieldRefs {
fieldValue := v.operation.ObjectFieldValue(fieldRef)

if fieldValue.Kind == ast.ValueKindNull {
objName := v.definition.InputObjectTypeDefinitionNameBytes(defRef)
fieldName := v.operation.ObjectFieldNameBytes(fieldRef)
v.Report.AddExternalError(operationreport.ErrOneOfInputObjectNullValue(objName, fieldName, fieldValue.Position))
return true
}

if fieldValue.Kind == ast.ValueKindVariable {
// For variables, check if the variable type is nullable
variableDefinitionRef, variableTypeRef, _, ok := v.operationVariableType(fieldValue.Ref)
if !ok {
continue
}

// Collect nullable variables
if v.operation.Types[variableTypeRef].TypeKind != ast.TypeKindNonNull {
objName := v.definition.InputObjectTypeDefinitionNameBytes(defRef)
fieldName := v.operation.ObjectFieldNameBytes(fieldRef)
variableName := v.operation.VariableValueNameBytes(fieldValue.Ref)
v.Report.AddExternalError(operationreport.ErrOneOfInputObjectNullableVariable(
objName, fieldName, variableName, fieldValue.Position,
v.operation.VariableDefinitions[variableDefinitionRef].VariableValue.Position))
return true
}
}
}

return false
}

func (v *valuesVisitor) objectFieldDefined(objectField, inputObjectTypeDefinition int) bool {
name := v.operation.ObjectFieldNameBytes(objectField)
for _, i := range v.definition.InputObjectTypeDefinitions[inputObjectTypeDefinition].InputFieldsDefinition.Refs {
Expand Down
Loading
Loading