Skip to content

Commit 253a8c5

Browse files
committed
Argument deprecation
1 parent 689132b commit 253a8c5

21 files changed

+317
-156
lines changed

guides/fields/arguments.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ def search_posts(category:)
6262
end
6363
```
6464

65+
**Experimental:** __Deprecated__ arguments can be marked by adding a `deprecation_reason:` keyword argument:
66+
67+
```ruby
68+
field :search_posts, [PostType], null: false do
69+
argument :name, String, required: false, deprecation_reason: "Use `query` instead."
70+
argument :query, String, required: false
71+
end
72+
```
73+
Note argument deprecation is a stage 2 GraphQL [proposal](https://github.com/graphql/graphql-spec/pull/525) so not all clients will leverage this information.
74+
6575
Use `as: :alternate_name` to use a different key from within your resolvers while
6676
exposing another key to clients.
6777

lib/graphql/argument.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ module GraphQL
33
# @api deprecated
44
class Argument
55
include GraphQL::Define::InstanceDefinable
6-
accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access
6+
accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access, :deprecation_reason
77
attr_reader :default_value
8-
attr_accessor :description, :name, :as
8+
attr_accessor :description, :name, :as, :deprecation_reason
99
attr_accessor :ast_node
1010
attr_accessor :method_access
1111
alias :graphql_name :name
1212

13-
ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access)
13+
ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access, :deprecation_reason)
1414

1515
# @api private
1616
module DefaultPrepare

lib/graphql/introspection.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,102 @@
11
# frozen_string_literal: true
22
module GraphQL
33
module Introspection
4+
def self.query(include_deprecated_args: false)
5+
# The introspection query to end all introspection queries, copied from
6+
# https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
7+
<<-QUERY
8+
query IntrospectionQuery {
9+
__schema {
10+
queryType { name }
11+
mutationType { name }
12+
subscriptionType { name }
13+
types {
14+
...FullType
15+
}
16+
directives {
17+
name
18+
description
19+
locations
20+
args {
21+
...InputValue
22+
}
23+
}
24+
}
25+
}
26+
fragment FullType on __Type {
27+
kind
28+
name
29+
description
30+
fields(includeDeprecated: true) {
31+
name
32+
description
33+
args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} {
34+
...InputValue
35+
}
36+
type {
37+
...TypeRef
38+
}
39+
isDeprecated
40+
deprecationReason
41+
}
42+
inputFields#{include_deprecated_args ? '(includeDeprecated: true)' : ''} {
43+
...InputValue
44+
}
45+
interfaces {
46+
...TypeRef
47+
}
48+
enumValues(includeDeprecated: true) {
49+
name
50+
description
51+
isDeprecated
52+
deprecationReason
53+
}
54+
possibleTypes {
55+
...TypeRef
56+
}
57+
}
58+
fragment InputValue on __InputValue {
59+
name
60+
description
61+
type { ...TypeRef }
62+
defaultValue
63+
#{include_deprecated_args ? 'isDeprecated' : ''}
64+
#{include_deprecated_args ? 'deprecationReason' : ''}
65+
}
66+
fragment TypeRef on __Type {
67+
kind
68+
name
69+
ofType {
70+
kind
71+
name
72+
ofType {
73+
kind
74+
name
75+
ofType {
76+
kind
77+
name
78+
ofType {
79+
kind
80+
name
81+
ofType {
82+
kind
83+
name
84+
ofType {
85+
kind
86+
name
87+
ofType {
88+
kind
89+
name
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
96+
}
97+
}
98+
QUERY
99+
end
4100
end
5101
end
6102

lib/graphql/introspection/field_type.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ class FieldType < Introspection::BaseObject
77
"a name, potentially a list of arguments, and a return type."
88
field :name, String, null: false
99
field :description, String, null: true
10-
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false
10+
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false do
11+
argument :include_deprecated, Boolean, required: false, default_value: false
12+
end
1113
field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
1214
field :is_deprecated, Boolean, null: false
1315
field :deprecation_reason, String, null: true
@@ -16,8 +18,10 @@ def is_deprecated
1618
!!@object.deprecation_reason
1719
end
1820

19-
def args
20-
@context.warden.arguments(@object)
21+
def args(include_deprecated:)
22+
args = @context.warden.arguments(@object)
23+
args = args.reject(&:deprecation_reason) unless include_deprecated
24+
args
2125
end
2226
end
2327
end

lib/graphql/introspection/input_value_type.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ class InputValueType < Introspection::BaseObject
1010
field :description, String, null: true
1111
field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
1212
field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", null: true
13+
field :is_deprecated, Boolean, null: false
14+
field :deprecation_reason, String, null: true
15+
16+
def is_deprecated
17+
!!@object.deprecation_reason
18+
end
1319

1420
def default_value
1521
if @object.default_value?
Lines changed: 6 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,7 @@
11
# frozen_string_literal: true
2-
# The introspection query to end all introspection queries, copied from
3-
# https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
4-
GraphQL::Introspection::INTROSPECTION_QUERY = "
5-
query IntrospectionQuery {
6-
__schema {
7-
queryType { name }
8-
mutationType { name }
9-
subscriptionType { name }
10-
types {
11-
...FullType
12-
}
13-
directives {
14-
name
15-
description
16-
locations
17-
args {
18-
...InputValue
19-
}
20-
}
21-
}
22-
}
23-
fragment FullType on __Type {
24-
kind
25-
name
26-
description
27-
fields(includeDeprecated: true) {
28-
name
29-
description
30-
args {
31-
...InputValue
32-
}
33-
type {
34-
...TypeRef
35-
}
36-
isDeprecated
37-
deprecationReason
38-
}
39-
inputFields {
40-
...InputValue
41-
}
42-
interfaces {
43-
...TypeRef
44-
}
45-
enumValues(includeDeprecated: true) {
46-
name
47-
description
48-
isDeprecated
49-
deprecationReason
50-
}
51-
possibleTypes {
52-
...TypeRef
53-
}
54-
}
55-
fragment InputValue on __InputValue {
56-
name
57-
description
58-
type { ...TypeRef }
59-
defaultValue
60-
}
61-
fragment TypeRef on __Type {
62-
kind
63-
name
64-
ofType {
65-
kind
66-
name
67-
ofType {
68-
kind
69-
name
70-
ofType {
71-
kind
72-
name
73-
ofType {
74-
kind
75-
name
76-
ofType {
77-
kind
78-
name
79-
ofType {
80-
kind
81-
name
82-
ofType {
83-
kind
84-
name
85-
}
86-
}
87-
}
88-
}
89-
}
90-
}
91-
}
92-
}
93-
"
2+
3+
# This query is used by graphql-client so don't add the includeDeprecated
4+
# argument for inputFields since the server may not support it. Two stage
5+
# introspection queries will be required to handle this in clients.
6+
GraphQL::Introspection::INTROSPECTION_QUERY = GraphQL::Introspection.query
7+

lib/graphql/introspection/type_type.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ class TypeType < Introspection::BaseObject
2222
field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], null: true do
2323
argument :include_deprecated, Boolean, required: false, default_value: false
2424
end
25-
field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: true
25+
field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: true do
26+
argument :include_deprecated, Boolean, required: false, default_value: false
27+
end
2628
field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), null: true
2729

2830
def name
@@ -55,9 +57,11 @@ def interfaces
5557
end
5658
end
5759

58-
def input_fields
60+
def input_fields(include_deprecated:)
5961
if @object.kind.input_object?
60-
@context.warden.arguments(@object)
62+
args = @context.warden.arguments(@object)
63+
args = args.reject(&:deprecation_reason) unless include_deprecated
64+
args
6165
else
6266
nil
6367
end

lib/graphql/schema.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,7 @@ def to_document(only: nil, except: nil, context: {})
832832
# @param except [<#call(member, ctx)>]
833833
# @return [Hash] GraphQL result
834834
def as_json(only: nil, except: nil, context: {})
835-
execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context).to_h
835+
execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
836836
end
837837

838838
# Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
@@ -880,7 +880,7 @@ def to_json(**args)
880880
# @param except [<#call(member, ctx)>]
881881
# @return [Hash] GraphQL result
882882
def as_json(only: nil, except: nil, context: {})
883-
execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context).to_h
883+
execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
884884
end
885885

886886
# Return the GraphQL IDL for the schema

lib/graphql/schema/argument.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ def from_resolver?
4545
# @param camelize [Boolean] if true, the name will be camelized when building the schema
4646
# @param from_resolver [Boolean] if true, a Resolver class defined this argument
4747
# @param method_access [Boolean] If false, don't build method access on legacy {Query::Arguments} instances.
48-
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, &definition_block)
48+
# @param deprecation_reason [String]
49+
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, deprecation_reason: nil, &definition_block)
4950
arg_name ||= name
5051
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
5152
@type_expr = type_expr || type
@@ -60,6 +61,7 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil
6061
@ast_node = ast_node
6162
@from_resolver = from_resolver
6263
@method_access = method_access
64+
@deprecation_reason = deprecation_reason
6365

6466
if definition_block
6567
if definition_block.arity == 1
@@ -89,6 +91,17 @@ def description(text = nil)
8991
end
9092
end
9193

94+
attr_writer :deprecation_reason
95+
96+
# @return [String] Deprecation reason for this argument
97+
def deprecation_reason(text = nil)
98+
if text
99+
@deprecation_reason = text
100+
else
101+
@deprecation_reason
102+
end
103+
end
104+
92105
def visible?(context)
93106
true
94107
end
@@ -143,6 +156,9 @@ def to_graphql
143156
if NO_DEFAULT != @default_value
144157
argument.default_value = @default_value
145158
end
159+
if @deprecation_reason
160+
argument.deprecation_reason = @deprecation_reason
161+
end
146162
argument
147163
end
148164

lib/graphql/schema/build_from_definition.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ def build_arguments(type_class, arguments, type_resolver)
261261
type: type_resolver.call(argument_defn.type),
262262
required: false,
263263
description: argument_defn.description,
264+
deprecation_reason: builder.build_deprecation_reason(argument_defn.directives),
264265
ast_node: argument_defn,
265266
camelize: false,
266267
method_access: false,

0 commit comments

Comments
 (0)