agent_patch
runs/2026-02-28__13-38-10__gpt-5-4/flux-pr-1308/flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/agent-logs/agent.patch
29866 bytes
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/asttransform/baseschema.go b/app/v2/pkg/asttransform/baseschema.go
index 45a6ebc..7450991 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/asttransform/baseschema.go
+++ b/app/v2/pkg/asttransform/baseschema.go
@@ -168,6 +168,9 @@ directive @deprecated(
directive @specifiedBy(url: String!) on SCALAR
+"Requires exactly one field to be supplied and that field must not be null."
+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
@@ -312,6 +315,8 @@ type __Type {
ofType: __Type
# may be non-null for custom SCALAR, otherwise null.
specifiedByURL: String
+ # must be non-null for INPUT_OBJECT, otherwise null.
+ isOneOf: Boolean
}
"An enum describing what kind of type a given '__Type' is."
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/astvalidation/operation_rule_all_variable_uses_defined.go b/app/v2/pkg/astvalidation/operation_rule_all_variable_uses_defined.go
index bc9d098..b868ff9 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/astvalidation/operation_rule_all_variable_uses_defined.go
+++ b/app/v2/pkg/astvalidation/operation_rule_all_variable_uses_defined.go
@@ -53,5 +53,5 @@ func (a *allVariableUsesDefinedVisitor) EnterArgument(ref int) {
// at this point we're safe to say this variable was not defined on the root operation of this argument
argumentName := a.operation.ArgumentNameBytes(ref)
- a.StopWithExternalErr(operationreport.ErrVariableNotDefinedOnArgument(variableName, argumentName))
+ a.StopWithExternalErr(operationreport.ErrVariableNotDefinedOnArgument(variableName, argumentName, a.operation.Arguments[ref].Value.Position))
}
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/astvalidation/operation_rule_values.go b/app/v2/pkg/astvalidation/operation_rule_values.go
index 5030117..f699b02 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/astvalidation/operation_rule_values.go
+++ b/app/v2/pkg/astvalidation/operation_rule_values.go
@@ -11,6 +11,8 @@ import (
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
)
+const oneOfDirectiveName = "oneOf"
+
// Values validates if values are used properly
func Values() Rule {
return func(walker *astvisitor.Walker) {
@@ -54,7 +56,7 @@ func (v *valuesVisitor) EnterArgument(ref int) {
if value.Kind == ast.ValueKindVariable {
_, exists := v.variableDefinition(value.Ref)
if !exists {
- v.StopWithExternalErr(operationreport.ErrVariableNotDefinedOnOperation(v.operation.VariableValueNameBytes(value.Ref), []byte("")))
+ v.StopWithExternalErr(operationreport.ErrVariableNotDefinedOnOperation(v.operation.VariableValueNameBytes(value.Ref), []byte(""), value.Position))
return
}
@@ -430,6 +432,29 @@ func (v *valuesVisitor) valueSatisfiesInputObjectTypeDefinition(value ast.Value,
return false
}
+ if v.definition.NodeHasDirectiveByNameString(ast.Node{Kind: ast.NodeKindInputObjectTypeDefinition, Ref: inputObjectTypeDefinition}, oneOfDirectiveName) {
+ return v.objectValueSatisfiesOneOfInputObject(value, inputObjectTypeDefinition) && valid
+ }
+
+ return true
+}
+
+func (v *valuesVisitor) objectValueSatisfiesOneOfInputObject(value ast.Value, inputObjectTypeDefinition int) bool {
+ fields := v.operation.ObjectValues[value.Ref].Refs
+ typeName := v.definition.InputObjectTypeDefinitionNameBytes(inputObjectTypeDefinition)
+
+ if len(fields) != 1 {
+ v.Report.AddExternalError(operationreport.ErrOneOfInputObjectMustSpecifyExactlyOneKey(typeName, value.Position))
+ return false
+ }
+
+ fieldValue := v.operation.ObjectFieldValue(fields[0])
+ if fieldValue.Kind == ast.ValueKindNull {
+ fieldName := v.operation.ObjectFieldNameBytes(fields[0])
+ v.Report.AddExternalError(operationreport.ErrOneOfInputObjectFieldMustBeNonNull(typeName, fieldName, fieldValue.Position))
+ return false
+ }
+
return true
}
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/astvalidation/operation_validation_test.go b/app/v2/pkg/astvalidation/operation_validation_test.go
index 57e056f..fa803d8 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/astvalidation/operation_validation_test.go
+++ b/app/v2/pkg/astvalidation/operation_validation_test.go
@@ -3960,6 +3960,46 @@ type Query {
}
`, Values(), Valid)
})
+ t.Run("oneOf input objects", func(t *testing.T) {
+ definition := `
+ directive @oneOf on INPUT_OBJECT
+
+ schema {
+ query: Query
+ }
+
+ scalar String
+ scalar Boolean
+
+ input Choice @oneOf {
+ cat: String
+ dog: String
+ }
+
+ type Query {
+ pet(input: Choice): Boolean
+ }
+ `
+
+ t.Run("valid with exactly one non-null field", func(t *testing.T) {
+ runWithDefinition(t, definition, `query { pet(input: {cat: "Garfield"}) }`, Values(), Valid)
+ })
+
+ t.Run("invalid with no fields", func(t *testing.T) {
+ runWithDefinition(t, definition, `query { pet(input: {}) }`, Values(), Invalid,
+ withValidationErrors(`OneOf Input Object "Choice" must specify exactly one key.`))
+ })
+
+ t.Run("invalid with multiple fields", func(t *testing.T) {
+ runWithDefinition(t, definition, `query { pet(input: {cat: "Garfield", dog: "Odie"}) }`, Values(), Invalid,
+ withValidationErrors(`OneOf Input Object "Choice" must specify exactly one key.`))
+ })
+
+ t.Run("invalid with null field", func(t *testing.T) {
+ runWithDefinition(t, definition, `query { pet(input: {cat: null}) }`, Values(), Invalid,
+ withValidationErrors(`Field "Choice.cat" must be non-null.`))
+ })
+ })
t.Run("with boolean input", func(t *testing.T) {
runWithDefinition(t, wundergraphSchema, `
query QueryWithBooleanInput($a: Boolean) {
@@ -3997,6 +4037,26 @@ type Query {
`Variable "$b" of type "Boolean" used in position expecting type "String"`,
))
})
+ t.Run("undefined variable includes source position", func(t *testing.T) {
+ definition := mustDocument(astparser.ParseGraphqlDocumentString(`
+ schema { query: Query }
+ scalar String
+ scalar Boolean
+ type Query { pet(name: String): Boolean }
+ `))
+ operation := mustDocument(astparser.ParseGraphqlDocumentString(`query { pet(name: $name) }`))
+ report := operationreport.Report{}
+
+ validator := &OperationValidator{}
+ validator.RegisterRule(Values())
+
+ result := validator.Validate(&operation, &definition, &report)
+
+ require.Equal(t, Invalid, result)
+ require.Len(t, report.ExternalErrors, 1)
+ assert.Equal(t, `variable: name not defined on operation: `, report.ExternalErrors[0].Message)
+ assert.Equal(t, []operationreport.Location{{Line: 1, Column: 19}}, report.ExternalErrors[0].Locations)
+ })
t.Run("with variables inside an input object", func(t *testing.T) {
run(t, `
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection.golden b/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection.golden
index 0064f2d..1043350 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection.golden
+++ b/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection.golden
@@ -350,6 +350,16 @@
],
"isRepeatable": false,
"__typename": "__Directive"
+ },
+ {
+ "name": "oneOf",
+ "description": "Requires exactly one field to be supplied and that field must not be null.",
+ "locations": [
+ "INPUT_OBJECT"
+ ],
+ "args": [],
+ "isRepeatable": false,
+ "__typename": "__Directive"
}
],
"__typename": "__Schema"
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection_with_custom_root_operation_types.golden b/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection_with_custom_root_operation_types.golden
index 0e8d299..d054853 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection_with_custom_root_operation_types.golden
+++ b/app/v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection_with_custom_root_operation_types.golden
@@ -498,6 +498,16 @@
],
"isRepeatable": false,
"__typename": "__Directive"
+ },
+ {
+ "name": "oneOf",
+ "description": "Requires exactly one field to be supplied and that field must not be null.",
+ "locations": [
+ "INPUT_OBJECT"
+ ],
+ "args": [],
+ "isRepeatable": false,
+ "__typename": "__Directive"
}
],
"__typename": "__Schema"
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/introspection_datasource/source_test.go b/app/v2/pkg/engine/datasource/introspection_datasource/source_test.go
index bb4a911..1009bcd 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/engine/datasource/introspection_datasource/source_test.go
+++ b/app/v2/pkg/engine/datasource/introspection_datasource/source_test.go
@@ -41,6 +41,36 @@ func TestSource_Load(t *testing.T) {
t.Run("schema introspection with custom root operation types", run(testSchemaWithCustomRootOperationTypes, `{"request_type":1}`, `schema_introspection_with_custom_root_operation_types`))
t.Run("type introspection", run(testSchema, `{"request_type":2,"type_name":"Query"}`, `type_introspection`))
t.Run("type introspection of not existing type", run(testSchema, `{"request_type":2,"type_name":"NotExisting"}`, `not_existing_type`))
+ t.Run("type introspection of oneOf input object", func(t *testing.T) {
+ def, report := astparser.ParseGraphqlDocumentString(`
+ directive @oneOf on INPUT_OBJECT
+
+ input PetInput @oneOf {
+ cat: String
+ dog: String
+ }
+
+ type Query {
+ pet(input: PetInput): Boolean
+ }
+ `)
+ require.False(t, report.HasErrors())
+ require.NoError(t, asttransform.MergeDefinitionWithBaseSchema(&def))
+
+ var data introspection.Data
+ gen := introspection.NewGenerator()
+ gen.Generate(&def, &report, &data)
+ require.False(t, report.HasErrors())
+
+ buf := &bytes.Buffer{}
+ source := &Source{introspectionData: &data}
+ require.NoError(t, source.Load(context.Background(), []byte(`{"request_type":2,"type_name":"PetInput"}`), buf))
+
+ var fullType introspection.FullType
+ require.NoError(t, json.Unmarshal(buf.Bytes(), &fullType))
+ require.NotNil(t, fullType.IsOneOf)
+ require.True(t, *fullType.IsOneOf)
+ })
}
const testSchema = `
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/converter.go b/app/v2/pkg/introspection/converter.go
index 22a37ea..8325e88 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/converter.go
+++ b/app/v2/pkg/introspection/converter.go
@@ -126,10 +126,16 @@ func (j *JsonConverter) importInputObject(fullType *FullType) error {
return err
}
- j.doc.ImportInputObjectTypeDefinition(
+ var directiveRefs []int
+ if fullType.IsOneOf != nil && *fullType.IsOneOf {
+ directiveRefs = append(directiveRefs, j.doc.ImportDirective(OneOfDirectiveName, nil))
+ }
+
+ j.doc.ImportInputObjectTypeDefinitionWithDirectives(
fullType.Name,
fullType.Description,
- argRefs)
+ argRefs,
+ directiveRefs)
return nil
}
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/converter_test.go b/app/v2/pkg/introspection/converter_test.go
index 0abc628..0bf7809 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/converter_test.go
+++ b/app/v2/pkg/introspection/converter_test.go
@@ -10,8 +10,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
"github.com/wundergraph/graphql-go-tools/v2/pkg/astparser"
"github.com/wundergraph/graphql-go-tools/v2/pkg/astprinter"
+ "github.com/wundergraph/graphql-go-tools/v2/pkg/asttransform"
"github.com/wundergraph/graphql-go-tools/v2/pkg/testing/goldie"
)
@@ -74,3 +76,37 @@ func BenchmarkJsonConverter_GraphQLDocument(b *testing.B) {
_, _ = converter.GraphQLDocument(buf)
}
}
+
+func TestJSONConverter_GraphQLDocument_OneOfInputObject(t *testing.T) {
+ definition, report := astparser.ParseGraphqlDocumentString(`
+ directive @oneOf on INPUT_OBJECT
+
+ input PetInput @oneOf {
+ cat: String
+ dog: String
+ }
+
+ type Query {
+ pet(input: PetInput): Boolean
+ }
+ `)
+ require.False(t, report.HasErrors())
+ require.NoError(t, asttransform.MergeDefinitionWithBaseSchema(&definition))
+
+ gen := NewGenerator()
+ var data Data
+ gen.Generate(&definition, &report, &data)
+ require.False(t, report.HasErrors())
+
+ outputPretty, err := json.MarshalIndent(data, "", " ")
+ require.NoError(t, err)
+
+ converter := JsonConverter{}
+ doc, err := converter.GraphQLDocument(bytes.NewBuffer(outputPretty))
+ require.NoError(t, err)
+
+ node, ok := doc.Index.FirstNodeByNameStr("PetInput")
+ require.True(t, ok)
+ require.Equal(t, ast.NodeKindInputObjectTypeDefinition, node.Kind)
+ require.True(t, doc.NodeHasDirectiveByNameString(node, OneOfDirectiveName))
+}
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/fixtures/starwars_introspected.golden b/app/v2/pkg/introspection/fixtures/starwars_introspected.golden
index 5bb9e05..f73c653 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/fixtures/starwars_introspected.golden
+++ b/app/v2/pkg/introspection/fixtures/starwars_introspected.golden
@@ -1588,7 +1588,8 @@
],
"interfaces": [],
"possibleTypes": [],
- "__typename": "__Type"
+ "__typename": "__Type",
+ "isOneOf": false
},
{
"kind": "INPUT_OBJECT",
@@ -1655,7 +1656,8 @@
],
"interfaces": [],
"possibleTypes": [],
- "__typename": "__Type"
+ "__typename": "__Type",
+ "isOneOf": false
},
{
"kind": "OBJECT",
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/generator.go b/app/v2/pkg/introspection/generator.go
index 820483f..435b292 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/generator.go
+++ b/app/v2/pkg/introspection/generator.go
@@ -13,6 +13,7 @@ const (
DeprecatedDirectiveName = "deprecated"
DeprecationReasonArgName = "reason"
SpecifiedByDirectiveName = "specifiedBy"
+ OneOfDirectiveName = "oneOf"
)
type Generator struct {
@@ -315,6 +316,8 @@ func (i *introspectionVisitor) EnterInputObjectTypeDefinition(ref int) {
i.currentType.Kind = INPUTOBJECT
i.currentType.Name = i.definition.InputObjectTypeDefinitionNameString(ref)
i.currentType.Description = i.definition.InputObjectTypeDefinitionDescriptionString(ref)
+ isOneOf := i.definition.NodeHasDirectiveByNameString(ast.Node{Kind: ast.NodeKindInputObjectTypeDefinition, Ref: ref}, OneOfDirectiveName)
+ i.currentType.IsOneOf = &isOneOf
}
func (i *introspectionVisitor) LeaveInputObjectTypeDefinition(ref int) {
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/generator_test.go b/app/v2/pkg/introspection/generator_test.go
index 690e849..800059b 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/generator_test.go
+++ b/app/v2/pkg/introspection/generator_test.go
@@ -6,8 +6,10 @@ import (
"testing"
"github.com/jensneuse/diffview"
+ "github.com/stretchr/testify/require"
"github.com/wundergraph/graphql-go-tools/v2/pkg/astparser"
+ "github.com/wundergraph/graphql-go-tools/v2/pkg/asttransform"
"github.com/wundergraph/graphql-go-tools/v2/pkg/testing/goldie"
)
@@ -78,3 +80,44 @@ func TestGenerator_Generate_Interfaces_Implementing_Interfaces(t *testing.T) {
diffview.NewGoland().DiffViewBytes("interfaces_implements_interfaces", fixture, outputPretty)
}
}
+
+func TestGenerator_Generate_OneOfInputObject(t *testing.T) {
+ definition, report := astparser.ParseGraphqlDocumentString(`
+ directive @oneOf on INPUT_OBJECT
+
+ input PetInput @oneOf {
+ cat: String
+ dog: String
+ }
+
+ type Query {
+ pet(input: PetInput): Boolean
+ }
+ `)
+ if report.HasErrors() {
+ t.Fatal(report)
+ }
+
+ require.NoError(t, asttransform.MergeDefinitionWithBaseSchema(&definition))
+
+ gen := NewGenerator()
+ var data Data
+ gen.Generate(&definition, &report, &data)
+ if report.HasErrors() {
+ t.Fatal(report)
+ }
+
+ petInput := data.Schema.TypeByName("PetInput")
+ require.NotNil(t, petInput)
+ require.NotNil(t, petInput.IsOneOf)
+ require.True(t, *petInput.IsOneOf)
+
+ var foundOneOf bool
+ for _, directive := range data.Schema.Directives {
+ if directive.Name == OneOfDirectiveName {
+ foundOneOf = true
+ break
+ }
+ }
+ require.True(t, foundOneOf)
+}
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/introspection.go b/app/v2/pkg/introspection/introspection.go
index c799e64..229b9fa 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/introspection/introspection.go
+++ b/app/v2/pkg/introspection/introspection.go
@@ -70,6 +70,8 @@ type FullType struct {
TypeName string `json:"__typename"`
// may be non-null for custom SCALAR, otherwise null.
SpecifiedByURL *string `json:"specifiedByURL,omitempty"`
+ // must be non-null for INPUT_OBJECT, otherwise null.
+ IsOneOf *bool `json:"isOneOf,omitempty"`
}
func NewFullType() *FullType {
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/operationreport/externalerror.go b/app/v2/pkg/operationreport/externalerror.go
index 8de9805..f572c5f 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/operationreport/externalerror.go
+++ b/app/v2/pkg/operationreport/externalerror.go
@@ -8,24 +8,26 @@ import (
)
const (
- NotCompatibleTypeErrMsg = "%s cannot represent value: %s"
- NotStringErrMsg = "%s cannot represent a non string value: %s"
- NotIntegerErrMsg = "%s cannot represent non-integer value: %s"
- BigIntegerErrMsg = "%s cannot represent non 32-bit signed integer value: %s"
- NotFloatErrMsg = "%s cannot represent non numeric value: %s"
- NotBooleanErrMsg = "%s cannot represent a non boolean value: %s"
- NotIDErrMsg = "%s cannot represent a non-string and non-integer value: %s"
- NotEnumErrMsg = `Enum "%s" cannot represent non-enum value: %s.`
- NotAnEnumMemberErrMsg = `Value "%s" does not exist in "%s" enum.`
- NullValueErrMsg = `Expected value of type "%s", found null.`
- UnknownArgumentOnDirectiveErrMsg = `Unknown argument "%s" on directive "@%s".`
- UnknownArgumentOnFieldErrMsg = `Unknown argument "%s" on field "%s.%s".`
- UnknownTypeErrMsg = `Unknown type "%s".`
- VariableIsNotInputTypeErrMsg = `Variable "$%s" cannot be non-input type "%s".`
- MissingRequiredFieldOfInputObjectErrMsg = `Field "%s.%s" of required type "%s" was not provided.`
- UnknownFieldOfInputObjectErrMsg = `Field "%s" is not defined by type "%s".`
- DuplicatedFieldInputObjectErrMsg = `There can be only one input field named "%s".`
- ValueIsNotAnInputObjectTypeErrMsg = `Expected value of type "%s", found %s.`
+ NotCompatibleTypeErrMsg = "%s cannot represent value: %s"
+ NotStringErrMsg = "%s cannot represent a non string value: %s"
+ NotIntegerErrMsg = "%s cannot represent non-integer value: %s"
+ BigIntegerErrMsg = "%s cannot represent non 32-bit signed integer value: %s"
+ NotFloatErrMsg = "%s cannot represent non numeric value: %s"
+ NotBooleanErrMsg = "%s cannot represent a non boolean value: %s"
+ NotIDErrMsg = "%s cannot represent a non-string and non-integer value: %s"
+ NotEnumErrMsg = `Enum "%s" cannot represent non-enum value: %s.`
+ NotAnEnumMemberErrMsg = `Value "%s" does not exist in "%s" enum.`
+ NullValueErrMsg = `Expected value of type "%s", found null.`
+ UnknownArgumentOnDirectiveErrMsg = `Unknown argument "%s" on directive "@%s".`
+ UnknownArgumentOnFieldErrMsg = `Unknown argument "%s" on field "%s.%s".`
+ UnknownTypeErrMsg = `Unknown type "%s".`
+ VariableIsNotInputTypeErrMsg = `Variable "$%s" cannot be non-input type "%s".`
+ MissingRequiredFieldOfInputObjectErrMsg = `Field "%s.%s" of required type "%s" was not provided.`
+ UnknownFieldOfInputObjectErrMsg = `Field "%s" is not defined by type "%s".`
+ DuplicatedFieldInputObjectErrMsg = `There can be only one input field named "%s".`
+ ValueIsNotAnInputObjectTypeErrMsg = `Expected value of type "%s", found %s.`
+ OneOfInputObjectMustSpecifyExactlyOneKeyErrMsg = `OneOf Input Object "%s" must specify exactly one key.`
+ OneOfInputObjectFieldMustBeNonNullErrMsg = `Field "%s.%s" must be non-null.`
)
type ExternalError struct {
@@ -305,8 +307,9 @@ func ErrVariableTypeDoesntSatisfyInputValueDefinition(value, inputType, expected
return err
}
-func ErrVariableNotDefinedOnOperation(variableName, operationName ast.ByteSlice) (err ExternalError) {
+func ErrVariableNotDefinedOnOperation(variableName, operationName ast.ByteSlice, position position.Position) (err ExternalError) {
err.Message = fmt.Sprintf("variable: %s not defined on operation: %s", variableName, operationName)
+ err.Locations = LocationsFromPosition(position)
return err
}
@@ -320,8 +323,21 @@ func ErrVariableMustBeUnique(variableName, operationName ast.ByteSlice) (err Ext
return err
}
-func ErrVariableNotDefinedOnArgument(variableName, argumentName ast.ByteSlice) (err ExternalError) {
+func ErrVariableNotDefinedOnArgument(variableName, argumentName ast.ByteSlice, position position.Position) (err ExternalError) {
err.Message = fmt.Sprintf("variable: %s not defined on argument: %s", variableName, argumentName)
+ err.Locations = LocationsFromPosition(position)
+ return err
+}
+
+func ErrOneOfInputObjectMustSpecifyExactlyOneKey(typeName ast.ByteSlice, position position.Position) (err ExternalError) {
+ err.Message = fmt.Sprintf(OneOfInputObjectMustSpecifyExactlyOneKeyErrMsg, typeName)
+ err.Locations = LocationsFromPosition(position)
+ return err
+}
+
+func ErrOneOfInputObjectFieldMustBeNonNull(typeName, fieldName ast.ByteSlice, position position.Position) (err ExternalError) {
+ err.Message = fmt.Sprintf(OneOfInputObjectFieldMustBeNonNullErrMsg, typeName, fieldName)
+ err.Locations = LocationsFromPosition(position)
return err
}
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/variablesvalidation/variablesvalidation.go b/app/v2/pkg/variablesvalidation/variablesvalidation.go
index 70bb603..7677314 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/variablesvalidation/variablesvalidation.go
+++ b/app/v2/pkg/variablesvalidation/variablesvalidation.go
@@ -20,6 +20,8 @@ type InvalidVariableError struct {
Message string
}
+const oneOfDirectiveName = "oneOf"
+
func (e *InvalidVariableError) Error() string {
return e.Message
}
@@ -325,6 +327,26 @@ func (v *variablesVisitor) renderVariableInvalidNullError(variableName []byte, t
v.err = v.newInvalidVariableError(fmt.Sprintf(`Variable "$%s" got invalid value null; Expected non-nullable type "%s" not to be null.`, string(variableName), typeName))
}
+func (v *variablesVisitor) renderVariableOneOfMustSpecifyExactlyOneKeyError(typeName []byte) {
+ variableName := string(v.currentVariableName)
+ invalidValue := string(v.currentVariableValue.MarshalTo(nil))
+ var path string
+ if len(v.path) > 1 {
+ path = fmt.Sprintf(` at "%s"`, v.renderPath())
+ }
+ v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; OneOf Input Object "%s" must specify exactly one key.`, v.invalidValueMessage(variableName, invalidValue), path, string(typeName)))
+}
+
+func (v *variablesVisitor) renderVariableOneOfFieldMustBeNonNullError(typeName, fieldName []byte) {
+ variableName := string(v.currentVariableName)
+ invalidValue := string(v.currentVariableValue.MarshalTo(nil))
+ var path string
+ if len(v.path) > 1 {
+ path = fmt.Sprintf(` at "%s"`, v.renderPath())
+ }
+ v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Field "%s.%s" must be non-null.`, v.invalidValueMessage(variableName, invalidValue), path, string(typeName), string(fieldName)))
+}
+
func (v *variablesVisitor) traverseFieldDefinitionType(fieldTypeDefinitionNodeKind ast.NodeKind, fieldName ast.ByteSlice, jsonValue *astjson.Value, typeRef, inputFieldRef int) {
if v.definition.TypeIsNonNull(typeRef) {
if jsonValue == nil || jsonValue.Type() == astjson.TypeNull {
@@ -415,6 +437,20 @@ func (v *variablesVisitor) traverseNamedTypeNode(jsonValue *astjson.Value, typeN
return
}
}
+ if v.definition.NodeHasDirectiveByNameString(fieldTypeDefinitionNode, oneOfDirectiveName) {
+ if len(keys) != 1 {
+ v.renderVariableOneOfMustSpecifyExactlyOneKeyError(typeName)
+ return
+ }
+ selectedFieldName := keys[0]
+ selectedFieldValue := jsonValue.Get(unsafebytes.BytesToString(selectedFieldName))
+ if selectedFieldValue == nil || selectedFieldValue.Type() == astjson.TypeNull {
+ v.pushObjectPath(selectedFieldName)
+ v.renderVariableOneOfFieldMustBeNonNullError(typeName, selectedFieldName)
+ v.popPath()
+ return
+ }
+ }
case ast.NodeKindScalarTypeDefinition:
switch unsafebytes.BytesToString(typeName) {
case "String":
diff --git a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/variablesvalidation/variablesvalidation_test.go b/app/v2/pkg/variablesvalidation/variablesvalidation_test.go
index 754a247..48344ca 100644
--- a/tmp/agent-patch-flux-pr-1308.1-of-1.2026-02-28__13-38-10__gpt-5-4/app/v2/pkg/variablesvalidation/variablesvalidation_test.go
+++ b/app/v2/pkg/variablesvalidation/variablesvalidation_test.go
@@ -509,6 +509,57 @@ func TestVariablesValidation(t *testing.T) {
assert.Equal(t, `Variable "$bar" of required type "Foo!" was not provided.`, err.Error())
})
+ t.Run("oneOf input object variable with exactly one non-null field", func(t *testing.T) {
+ tc := testCase{
+ schema: `directive @oneOf on INPUT_OBJECT
+ input PetInput @oneOf { cat: String dog: String }
+ type Query { hello(arg: PetInput!): String }`,
+ operation: `query Foo($input: PetInput!) { hello(arg: $input) }`,
+ variables: `{"input":{"cat":"Garfield"}}`,
+ }
+ err := runTest(t, tc)
+ require.NoError(t, err)
+ })
+
+ t.Run("oneOf input object variable with no fields", func(t *testing.T) {
+ tc := testCase{
+ schema: `directive @oneOf on INPUT_OBJECT
+ input PetInput @oneOf { cat: String dog: String }
+ type Query { hello(arg: PetInput!): String }`,
+ operation: `query Foo($input: PetInput!) { hello(arg: $input) }`,
+ variables: `{"input":{}}`,
+ }
+ err := runTest(t, tc)
+ require.Error(t, err)
+ assert.Equal(t, `Variable "$input" got invalid value; OneOf Input Object "PetInput" must specify exactly one key.`, err.Error())
+ })
+
+ t.Run("oneOf input object variable with multiple fields", func(t *testing.T) {
+ tc := testCase{
+ schema: `directive @oneOf on INPUT_OBJECT
+ input PetInput @oneOf { cat: String dog: String }
+ type Query { hello(arg: PetInput!): String }`,
+ operation: `query Foo($input: PetInput!) { hello(arg: $input) }`,
+ variables: `{"input":{"cat":"Garfield","dog":"Odie"}}`,
+ }
+ err := runTest(t, tc)
+ require.Error(t, err)
+ assert.Equal(t, `Variable "$input" got invalid value; OneOf Input Object "PetInput" must specify exactly one key.`, err.Error())
+ })
+
+ t.Run("oneOf input object variable with null field", func(t *testing.T) {
+ tc := testCase{
+ schema: `directive @oneOf on INPUT_OBJECT
+ input PetInput @oneOf { cat: String dog: String }
+ type Query { hello(arg: PetInput!): String }`,
+ operation: `query Foo($input: PetInput!) { hello(arg: $input) }`,
+ variables: `{"input":{"cat":null}}`,
+ }
+ err := runTest(t, tc)
+ require.Error(t, err)
+ assert.Equal(t, `Variable "$input" got invalid value at "input.cat"; Field "PetInput.cat" must be non-null.`, err.Error())
+ })
+
t.Run("required string list field argument not provided", func(t *testing.T) {
tc := testCase{
schema: `type Query { hello(arg: [String]!): String }`,