diff --git a/codegen/testserver/followschema/introspection_test.go b/codegen/testserver/followschema/introspection_test.go index c1bbdfbd9da..26583a4f885 100644 --- a/codegen/testserver/followschema/introspection_test.go +++ b/codegen/testserver/followschema/introspection_test.go @@ -67,6 +67,45 @@ func TestIntrospection(t *testing.T) { require.Equal(t, "id", resp.Type.Fields[0].Name) require.Nil(t, resp.Type.Fields[0].DeprecationReason) }) + + t.Run("deprecated directive on field arguments", func(t *testing.T) { + var resp struct { + Type struct { + Fields []struct { + Name string + Args []struct { + Name string + DeprecationReason *string + } + } + } `json:"__type"` + } + + err := c.Post( + `{ __type(name:"Query") { fields { name args { name deprecationReason }}}}`, + &resp, + ) + require.NoError(t, err) + + var args []struct { + Name string + DeprecationReason *string + } + for _, f := range resp.Type.Fields { + if f.Name == "fieldWithDeprecatedArg" { + args = f.Args + break + } + } + + require.Len(t, args, 2) + require.Equal(t, "oldArg", args[0].Name) + require.NotNil(t, args[0].DeprecationReason) + require.Equal(t, "old arg", *args[0].DeprecationReason) + + require.Equal(t, "newArg", args[1].Name) + require.Nil(t, args[1].DeprecationReason) + }) }) t.Run("disabled by middleware", func(t *testing.T) { diff --git a/codegen/testserver/followschema/resolver.go b/codegen/testserver/followschema/resolver.go index afb746fd2fb..5799d6a37d4 100644 --- a/codegen/testserver/followschema/resolver.go +++ b/codegen/testserver/followschema/resolver.go @@ -192,6 +192,11 @@ func (r *queryResolver) DeprecatedField(ctx context.Context) (string, error) { panic("not implemented") } +// FieldWithDeprecatedArg is the resolver for the fieldWithDeprecatedArg field. +func (r *queryResolver) FieldWithDeprecatedArg(ctx context.Context, oldArg *int, newArg *int) (*string, error) { + panic("not implemented") +} + // Overlapping is the resolver for the overlapping field. func (r *queryResolver) Overlapping(ctx context.Context) (*OverlappingFields, error) { panic("not implemented") diff --git a/codegen/testserver/followschema/root_.generated.go b/codegen/testserver/followschema/root_.generated.go index 1954cb72a01..864f4184ff6 100644 --- a/codegen/testserver/followschema/root_.generated.go +++ b/codegen/testserver/followschema/root_.generated.go @@ -350,6 +350,7 @@ type ComplexityRoot struct { ErrorList func(childComplexity int) int Errors func(childComplexity int) int Fallback func(childComplexity int, arg FallbackToStringEncoding) int + FieldWithDeprecatedArg func(childComplexity int, oldArg *int, newArg *int) int FilterProducts func(childComplexity int, query *string, category *string, minPrice *int) int FindProducts func(childComplexity int, query *string, category *string, minPrice *int) int Infinity func(childComplexity int) int @@ -1526,6 +1527,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Query.Fallback(childComplexity, args["arg"].(FallbackToStringEncoding)), true + case "Query.fieldWithDeprecatedArg": + if e.complexity.Query.FieldWithDeprecatedArg == nil { + break + } + + args, err := ec.field_Query_fieldWithDeprecatedArg_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.FieldWithDeprecatedArg(childComplexity, args["oldArg"].(*int), args["newArg"].(*int)), true + case "Query.filterProducts": if e.complexity.Query.FilterProducts == nil { break @@ -2772,6 +2785,7 @@ type Query { shapeUnion: ShapeUnion! autobind: Autobind deprecatedField: String! @deprecated(reason: "test deprecated directive") + fieldWithDeprecatedArg(oldArg: Int @deprecated(reason: "old arg"), newArg: Int): String overlapping: OverlappingFields defaultParameters(falsyBoolean: Boolean = false, truthyBoolean: Boolean = true): DefaultParametersMirror! deferSingle: DeferModel diff --git a/codegen/testserver/followschema/schema.generated.go b/codegen/testserver/followschema/schema.generated.go index 9084913f346..81ea5c193cb 100644 --- a/codegen/testserver/followschema/schema.generated.go +++ b/codegen/testserver/followschema/schema.generated.go @@ -45,6 +45,7 @@ type QueryResolver interface { ShapeUnion(ctx context.Context) (ShapeUnion, error) Autobind(ctx context.Context) (*Autobind, error) DeprecatedField(ctx context.Context) (string, error) + FieldWithDeprecatedArg(ctx context.Context, oldArg *int, newArg *int) (*string, error) Overlapping(ctx context.Context) (*OverlappingFields, error) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) DeferSingle(ctx context.Context) (*DeferModel, error) @@ -601,6 +602,22 @@ func (ec *executionContext) field_Query_fallback_args(ctx context.Context, rawAr return args, nil } +func (ec *executionContext) field_Query_fieldWithDeprecatedArg_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "oldArg", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["oldArg"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "newArg", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["newArg"] = arg1 + return args, nil +} + func (ec *executionContext) field_Query_filterProducts_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -2285,6 +2302,49 @@ func (ec *executionContext) fieldContext_Query_deprecatedField(_ context.Context return fc, nil } +func (ec *executionContext) _Query_fieldWithDeprecatedArg(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Query_fieldWithDeprecatedArg, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.Query().FieldWithDeprecatedArg(ctx, fc.Args["oldArg"].(*int), fc.Args["newArg"].(*int)) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + return ec._fieldMiddleware(ctx, nil, next) + }, + ec.marshalOString2ᚖstring, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_Query_fieldWithDeprecatedArg(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_fieldWithDeprecatedArg_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query_overlapping(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -6607,6 +6667,25 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "fieldWithDeprecatedArg": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_fieldWithDeprecatedArg(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "overlapping": field := field diff --git a/codegen/testserver/followschema/schema.graphql b/codegen/testserver/followschema/schema.graphql index 79861508980..4ed9dbad8a2 100644 --- a/codegen/testserver/followschema/schema.graphql +++ b/codegen/testserver/followschema/schema.graphql @@ -29,6 +29,7 @@ type Query { shapeUnion: ShapeUnion! autobind: Autobind deprecatedField: String! @deprecated(reason: "test deprecated directive") + fieldWithDeprecatedArg(oldArg: Int @deprecated(reason: "old arg"), newArg: Int): String } type Subscription { diff --git a/codegen/testserver/followschema/stub.go b/codegen/testserver/followschema/stub.go index 2147fd900ef..4bc424ee8c0 100644 --- a/codegen/testserver/followschema/stub.go +++ b/codegen/testserver/followschema/stub.go @@ -70,6 +70,7 @@ type Stub struct { ShapeUnion func(ctx context.Context) (ShapeUnion, error) Autobind func(ctx context.Context) (*Autobind, error) DeprecatedField func(ctx context.Context) (string, error) + FieldWithDeprecatedArg func(ctx context.Context, oldArg *int, newArg *int) (*string, error) Overlapping func(ctx context.Context) (*OverlappingFields, error) DefaultParameters func(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) DeferSingle func(ctx context.Context) (*DeferModel, error) @@ -359,6 +360,9 @@ func (r *stubQuery) Autobind(ctx context.Context) (*Autobind, error) { func (r *stubQuery) DeprecatedField(ctx context.Context) (string, error) { return r.QueryResolver.DeprecatedField(ctx) } +func (r *stubQuery) FieldWithDeprecatedArg(ctx context.Context, oldArg *int, newArg *int) (*string, error) { + return r.QueryResolver.FieldWithDeprecatedArg(ctx, oldArg, newArg) +} func (r *stubQuery) Overlapping(ctx context.Context) (*OverlappingFields, error) { return r.QueryResolver.Overlapping(ctx) } diff --git a/codegen/testserver/singlefile/generated.go b/codegen/testserver/singlefile/generated.go index 8f233e9d774..9c0863dc71e 100644 --- a/codegen/testserver/singlefile/generated.go +++ b/codegen/testserver/singlefile/generated.go @@ -359,6 +359,7 @@ type ComplexityRoot struct { ErrorList func(childComplexity int) int Errors func(childComplexity int) int Fallback func(childComplexity int, arg FallbackToStringEncoding) int + FieldWithDeprecatedArg func(childComplexity int, oldArg *int, newArg *int) int FilterProducts func(childComplexity int, query *string, category *string, minPrice *int) int FindProducts func(childComplexity int, query *string, category *string, minPrice *int) int Infinity func(childComplexity int) int @@ -565,6 +566,7 @@ type QueryResolver interface { ShapeUnion(ctx context.Context) (ShapeUnion, error) Autobind(ctx context.Context) (*Autobind, error) DeprecatedField(ctx context.Context) (string, error) + FieldWithDeprecatedArg(ctx context.Context, oldArg *int, newArg *int) (*string, error) Overlapping(ctx context.Context) (*OverlappingFields, error) DefaultParameters(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) DeferSingle(ctx context.Context) (*DeferModel, error) @@ -1607,6 +1609,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.complexity.Query.Fallback(childComplexity, args["arg"].(FallbackToStringEncoding)), true + case "Query.fieldWithDeprecatedArg": + if e.complexity.Query.FieldWithDeprecatedArg == nil { + break + } + + args, err := ec.field_Query_fieldWithDeprecatedArg_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.FieldWithDeprecatedArg(childComplexity, args["oldArg"].(*int), args["newArg"].(*int)), true case "Query.filterProducts": if e.complexity.Query.FilterProducts == nil { break @@ -2780,6 +2793,7 @@ type Query { shapeUnion: ShapeUnion! autobind: Autobind deprecatedField: String! @deprecated(reason: "test deprecated directive") + fieldWithDeprecatedArg(oldArg: Int @deprecated(reason: "old arg"), newArg: Int): String overlapping: OverlappingFields defaultParameters(falsyBoolean: Boolean = false, truthyBoolean: Boolean = true): DefaultParametersMirror! deferSingle: DeferModel @@ -3647,6 +3661,22 @@ func (ec *executionContext) field_Query_fallback_args(ctx context.Context, rawAr return args, nil } +func (ec *executionContext) field_Query_fieldWithDeprecatedArg_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "oldArg", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["oldArg"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "newArg", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["newArg"] = arg1 + return args, nil +} + func (ec *executionContext) field_Query_filterProducts_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -8532,6 +8562,49 @@ func (ec *executionContext) fieldContext_Query_deprecatedField(_ context.Context return fc, nil } +func (ec *executionContext) _Query_fieldWithDeprecatedArg(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Query_fieldWithDeprecatedArg, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.Query().FieldWithDeprecatedArg(ctx, fc.Args["oldArg"].(*int), fc.Args["newArg"].(*int)) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + return ec._fieldMiddleware(ctx, nil, next) + }, + ec.marshalOString2ᚖstring, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_Query_fieldWithDeprecatedArg(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_fieldWithDeprecatedArg_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query_overlapping(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -18591,6 +18664,25 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "fieldWithDeprecatedArg": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_fieldWithDeprecatedArg(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "overlapping": field := field diff --git a/codegen/testserver/singlefile/introspection_test.go b/codegen/testserver/singlefile/introspection_test.go index 946eee4a8fe..9e4cbb59955 100644 --- a/codegen/testserver/singlefile/introspection_test.go +++ b/codegen/testserver/singlefile/introspection_test.go @@ -68,6 +68,45 @@ func TestIntrospection(t *testing.T) { require.Equal(t, "id", resp.Type.Fields[0].Name) require.Nil(t, resp.Type.Fields[0].DeprecationReason) }) + + t.Run("deprecated arguments", func(t *testing.T) { + var resp struct { + Type struct { + Fields []struct { + Name string + Args []struct { + Name string + DeprecationReason *string + } + } + } `json:"__type"` + } + + err := c.Post( + `{ __type(name:"Query") { fields { name args { name deprecationReason }}}}`, + &resp, + ) + require.NoError(t, err) + + var args []struct { + Name string + DeprecationReason *string + } + for _, f := range resp.Type.Fields { + if f.Name == "fieldWithDeprecatedArg" { + args = f.Args + break + } + } + + require.Len(t, args, 2) + require.Equal(t, "oldArg", args[0].Name) + require.NotNil(t, args[0].DeprecationReason) + require.Equal(t, "old arg", *args[0].DeprecationReason) + + require.Equal(t, "newArg", args[1].Name) + require.Nil(t, args[1].DeprecationReason) + }) }) t.Run("disabled by middleware", func(t *testing.T) { diff --git a/codegen/testserver/singlefile/resolver.go b/codegen/testserver/singlefile/resolver.go index d5cdff70007..70e91c87eca 100644 --- a/codegen/testserver/singlefile/resolver.go +++ b/codegen/testserver/singlefile/resolver.go @@ -192,6 +192,11 @@ func (r *queryResolver) DeprecatedField(ctx context.Context) (string, error) { panic("not implemented") } +// FieldWithDeprecatedArg is the resolver for the fieldWithDeprecatedArg field. +func (r *queryResolver) FieldWithDeprecatedArg(ctx context.Context, oldArg *int, newArg *int) (*string, error) { + panic("not implemented") +} + // Overlapping is the resolver for the overlapping field. func (r *queryResolver) Overlapping(ctx context.Context) (*OverlappingFields, error) { panic("not implemented") diff --git a/codegen/testserver/singlefile/schema.graphql b/codegen/testserver/singlefile/schema.graphql index c139bd7dfee..c40a716db60 100644 --- a/codegen/testserver/singlefile/schema.graphql +++ b/codegen/testserver/singlefile/schema.graphql @@ -29,6 +29,7 @@ type Query { shapeUnion: ShapeUnion! autobind: Autobind deprecatedField: String! @deprecated(reason: "test deprecated directive") + fieldWithDeprecatedArg(oldArg: Int @deprecated(reason: "old arg"), newArg: Int): String } type Subscription { diff --git a/codegen/testserver/singlefile/stub.go b/codegen/testserver/singlefile/stub.go index 0c0cae1fc4d..7616a524135 100644 --- a/codegen/testserver/singlefile/stub.go +++ b/codegen/testserver/singlefile/stub.go @@ -70,6 +70,7 @@ type Stub struct { ShapeUnion func(ctx context.Context) (ShapeUnion, error) Autobind func(ctx context.Context) (*Autobind, error) DeprecatedField func(ctx context.Context) (string, error) + FieldWithDeprecatedArg func(ctx context.Context, oldArg *int, newArg *int) (*string, error) Overlapping func(ctx context.Context) (*OverlappingFields, error) DefaultParameters func(ctx context.Context, falsyBoolean *bool, truthyBoolean *bool) (*DefaultParametersMirror, error) DeferSingle func(ctx context.Context) (*DeferModel, error) @@ -359,6 +360,9 @@ func (r *stubQuery) Autobind(ctx context.Context) (*Autobind, error) { func (r *stubQuery) DeprecatedField(ctx context.Context) (string, error) { return r.QueryResolver.DeprecatedField(ctx) } +func (r *stubQuery) FieldWithDeprecatedArg(ctx context.Context, oldArg *int, newArg *int) (*string, error) { + return r.QueryResolver.FieldWithDeprecatedArg(ctx, oldArg, newArg) +} func (r *stubQuery) Overlapping(ctx context.Context) (*OverlappingFields, error) { return r.QueryResolver.Overlapping(ctx) } diff --git a/graphql/introspection/type.go b/graphql/introspection/type.go index c4145ba11a1..d39fceeb0dd 100644 --- a/graphql/introspection/type.go +++ b/graphql/introspection/type.go @@ -81,7 +81,7 @@ func (t *Type) Fields(includeDeprecated bool) []Field { Name: arg.Name, description: arg.Description, DefaultValue: defaultValue(arg.DefaultValue), - deprecation: f.Directives.ForName("deprecated"), + deprecation: arg.Directives.ForName("deprecated"), }) }