Skip to content

Commit

Permalink
Argument deprecation
Browse files Browse the repository at this point in the history
  • Loading branch information
jturkel committed Aug 11, 2020
1 parent 689132b commit 253a8c5
Show file tree
Hide file tree
Showing 21 changed files with 317 additions and 156 deletions.
10 changes: 10 additions & 0 deletions guides/fields/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ def search_posts(category:)
end
```

**Experimental:** __Deprecated__ arguments can be marked by adding a `deprecation_reason:` keyword argument:

```ruby
field :search_posts, [PostType], null: false do
argument :name, String, required: false, deprecation_reason: "Use `query` instead."
argument :query, String, required: false
end
```
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.

Use `as: :alternate_name` to use a different key from within your resolvers while
exposing another key to clients.

Expand Down
6 changes: 3 additions & 3 deletions lib/graphql/argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ module GraphQL
# @api deprecated
class Argument
include GraphQL::Define::InstanceDefinable
accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access
accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access, :deprecation_reason
attr_reader :default_value
attr_accessor :description, :name, :as
attr_accessor :description, :name, :as, :deprecation_reason
attr_accessor :ast_node
attr_accessor :method_access
alias :graphql_name :name

ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access)
ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access, :deprecation_reason)

# @api private
module DefaultPrepare
Expand Down
96 changes: 96 additions & 0 deletions lib/graphql/introspection.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,102 @@
# frozen_string_literal: true
module GraphQL
module Introspection
def self.query(include_deprecated_args: false)
# The introspection query to end all introspection queries, copied from
# https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
<<-QUERY
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields#{include_deprecated_args ? '(includeDeprecated: true)' : ''} {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
#{include_deprecated_args ? 'isDeprecated' : ''}
#{include_deprecated_args ? 'deprecationReason' : ''}
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
QUERY
end
end
end

Expand Down
10 changes: 7 additions & 3 deletions lib/graphql/introspection/field_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ class FieldType < Introspection::BaseObject
"a name, potentially a list of arguments, and a return type."
field :name, String, null: false
field :description, String, null: true
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false do
argument :include_deprecated, Boolean, required: false, default_value: false
end
field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
field :is_deprecated, Boolean, null: false
field :deprecation_reason, String, null: true
Expand All @@ -16,8 +18,10 @@ def is_deprecated
!!@object.deprecation_reason
end

def args
@context.warden.arguments(@object)
def args(include_deprecated:)
args = @context.warden.arguments(@object)
args = args.reject(&:deprecation_reason) unless include_deprecated
args
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions lib/graphql/introspection/input_value_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ class InputValueType < Introspection::BaseObject
field :description, String, null: true
field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", null: true
field :is_deprecated, Boolean, null: false
field :deprecation_reason, String, null: true

def is_deprecated
!!@object.deprecation_reason
end

def default_value
if @object.default_value?
Expand Down
98 changes: 6 additions & 92 deletions lib/graphql/introspection/introspection_query.rb
Original file line number Diff line number Diff line change
@@ -1,93 +1,7 @@
# frozen_string_literal: true
# The introspection query to end all introspection queries, copied from
# https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
GraphQL::Introspection::INTROSPECTION_QUERY = "
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
"

# This query is used by graphql-client so don't add the includeDeprecated
# argument for inputFields since the server may not support it. Two stage
# introspection queries will be required to handle this in clients.
GraphQL::Introspection::INTROSPECTION_QUERY = GraphQL::Introspection.query

10 changes: 7 additions & 3 deletions lib/graphql/introspection/type_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ class TypeType < Introspection::BaseObject
field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], null: true do
argument :include_deprecated, Boolean, required: false, default_value: false
end
field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: true
field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: true do
argument :include_deprecated, Boolean, required: false, default_value: false
end
field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), null: true

def name
Expand Down Expand Up @@ -55,9 +57,11 @@ def interfaces
end
end

def input_fields
def input_fields(include_deprecated:)
if @object.kind.input_object?
@context.warden.arguments(@object)
args = @context.warden.arguments(@object)
args = args.reject(&:deprecation_reason) unless include_deprecated
args
else
nil
end
Expand Down
4 changes: 2 additions & 2 deletions lib/graphql/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ def to_document(only: nil, except: nil, context: {})
# @param except [<#call(member, ctx)>]
# @return [Hash] GraphQL result
def as_json(only: nil, except: nil, context: {})
execute(Introspection::INTROSPECTION_QUERY, only: only, except: except, context: context).to_h
execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
end

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

# Return the GraphQL IDL for the schema
Expand Down
18 changes: 17 additions & 1 deletion lib/graphql/schema/argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def from_resolver?
# @param camelize [Boolean] if true, the name will be camelized when building the schema
# @param from_resolver [Boolean] if true, a Resolver class defined this argument
# @param method_access [Boolean] If false, don't build method access on legacy {Query::Arguments} instances.
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)
# @param deprecation_reason [String]
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)
arg_name ||= name
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
@type_expr = type_expr || type
Expand All @@ -60,6 +61,7 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil
@ast_node = ast_node
@from_resolver = from_resolver
@method_access = method_access
@deprecation_reason = deprecation_reason

if definition_block
if definition_block.arity == 1
Expand Down Expand Up @@ -89,6 +91,17 @@ def description(text = nil)
end
end

attr_writer :deprecation_reason

# @return [String] Deprecation reason for this argument
def deprecation_reason(text = nil)
if text
@deprecation_reason = text
else
@deprecation_reason
end
end

def visible?(context)
true
end
Expand Down Expand Up @@ -143,6 +156,9 @@ def to_graphql
if NO_DEFAULT != @default_value
argument.default_value = @default_value
end
if @deprecation_reason
argument.deprecation_reason = @deprecation_reason
end
argument
end

Expand Down
1 change: 1 addition & 0 deletions lib/graphql/schema/build_from_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ def build_arguments(type_class, arguments, type_resolver)
type: type_resolver.call(argument_defn.type),
required: false,
description: argument_defn.description,
deprecation_reason: builder.build_deprecation_reason(argument_defn.directives),
ast_node: argument_defn,
camelize: false,
method_access: false,
Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/schema/directive/deprecated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Schema
class Directive < GraphQL::Schema::Member
class Deprecated < GraphQL::Schema::Directive
description "Marks an element of a GraphQL schema as no longer supported."
locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE)
locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION)

reason_description = "Explains why this element was deprecated, usually also including a "\
"suggestion for how to access supported similar data. Formatted "\
Expand Down
1 change: 1 addition & 0 deletions lib/graphql/schema/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ def build_arguments(arg_owner, args, type_resolver)
kwargs = {
type: type_resolver.call(arg["type"]),
description: arg["description"],
deprecation_reason: arg["deprecationReason"],
required: false,
method_access: false,
camelize: false,
Expand Down
2 changes: 1 addition & 1 deletion spec/graphql/introspection/directive_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"args" => [
{"name"=>"reason", "type"=>{"kind"=>"SCALAR", "name"=>"String", "ofType"=>nil}}
],
"locations"=>["FIELD_DEFINITION", "ENUM_VALUE"],
"locations"=>["FIELD_DEFINITION", "ENUM_VALUE", "ARGUMENT_DEFINITION", "INPUT_FIELD_DEFINITION"],
"onField" => false,
"onFragment" => false,
"onOperation" => false,
Expand Down
Loading

0 comments on commit 253a8c5

Please sign in to comment.