-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add deprecations to IDL #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
GraphQL::Directive::DeprecatedDirective = GraphQL::Directive.define do | ||
name "deprecated" | ||
description "Marks an element of a GraphQL schema as no longer supported." | ||
locations([GraphQL::Directive::FIELD_DEFINITION, GraphQL::Directive::ENUM_VALUE]) | ||
|
||
reason_description = "Explains why this element was deprecated, usually also including a "\ | ||
"suggestion for how to access supported similar data. Formatted "\ | ||
"in [Markdown](https://daringfireball.net/projects/markdown/)." | ||
|
||
argument :reason, !GraphQL::STRING_TYPE, reason_description, default_value: GraphQL::Directive::DEFAULT_DEPRECATION_REASON | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ module Printer | |
# Return a GraphQL schema string for the defined types in the schema | ||
# @param schema [GraphQL::Schema] | ||
def print_schema(schema) | ||
print_filtered_schema(schema, method(:is_defined_type)) | ||
print_filtered_schema(schema, lambda { |n| !is_spec_directive(n) }, method(:is_defined_type)) | ||
end | ||
|
||
# Return the GraphQL schema string for the introspection type system | ||
|
@@ -21,16 +21,20 @@ def print_introspection_schema | |
name "Query" | ||
end | ||
schema = GraphQL::Schema.define(query: query_root) | ||
print_filtered_schema(schema, method(:is_introspection_type)) | ||
print_filtered_schema(schema, method(:is_spec_directive), method(:is_introspection_type)) | ||
end | ||
|
||
private | ||
|
||
def print_filtered_schema(schema, type_filter) | ||
def print_filtered_schema(schema, directive_filter, type_filter) | ||
directives = schema.directives.values.select{ |directive| directive_filter.call(directive) } | ||
directive_definitions = directives.map{ |directive| print_directive(directive) } | ||
|
||
types = schema.types.values.select{ |type| type_filter.call(type) }.sort_by(&:name) | ||
type_definitions = types.map{ |type| print_type(type) } | ||
|
||
[print_schema_definition(schema)].concat(type_definitions).join("\n\n") | ||
[print_schema_definition(schema)].concat(directive_definitions) | ||
.concat(type_definitions).join("\n\n") | ||
end | ||
|
||
def print_schema_definition(schema) | ||
|
@@ -44,6 +48,10 @@ def print_schema_definition(schema) | |
BUILTIN_SCALARS = Set.new(["String", "Boolean", "Int", "Float", "ID"]) | ||
private_constant :BUILTIN_SCALARS | ||
|
||
def is_spec_directive(directive) | ||
['skip', 'include', 'deprecated'].include?(directive.name) | ||
end | ||
|
||
def is_introspection_type(type) | ||
type.name.start_with?("__") | ||
end | ||
|
@@ -56,12 +64,27 @@ def print_type(type) | |
TypeKindPrinters::STRATEGIES.fetch(type.kind).print(type) | ||
end | ||
|
||
def print_directive(directive) | ||
TypeKindPrinters::DirectivePrinter.print(directive) | ||
end | ||
|
||
module TypeKindPrinters | ||
module FieldPrinter | ||
def print_fields(type) | ||
type.all_fields.map{ |field| " #{field.name}#{print_args(field)}: #{field.type}" }.join("\n") | ||
module DeprecatedPrinter | ||
def print_deprecated(field_or_enum_value) | ||
return unless field_or_enum_value.deprecation_reason | ||
|
||
case field_or_enum_value.deprecation_reason | ||
when nil | ||
'' | ||
when '', GraphQL::Directive::DEFAULT_DEPRECATION_REASON | ||
' @deprecated' | ||
else | ||
" @deprecated(reason: #{field_or_enum_value.deprecation_reason.to_s.inspect})" | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make sure I understand, is this equivalent to three, mutually-exclusive branches, ie case field_or_enum_value.deprecation_reason
when nil
""
when ""
" @deprecated"
else
" @deprecated(...)"
end ? I guess there's a different behavior with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the JS version handles the "default deprecation reason" specially, should we do the same? (eg There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. The code is clearer with the |
||
end | ||
end | ||
|
||
module ArgsPrinter | ||
def print_args(field) | ||
return if field.arguments.empty? | ||
"(#{field.arguments.values.map{ |arg| print_input_value(arg) }.join(", ")})" | ||
|
@@ -105,6 +128,24 @@ def print_value(value, type) | |
end | ||
end | ||
|
||
module FieldPrinter | ||
include DeprecatedPrinter | ||
include ArgsPrinter | ||
def print_fields(type) | ||
type.all_fields.map{ |field| | ||
" #{field.name}#{print_args(field)}: #{field.type}#{print_deprecated(field)}" | ||
}.join("\n") | ||
end | ||
end | ||
|
||
class DirectivePrinter | ||
extend ArgsPrinter | ||
def self.print(directive) | ||
"directive @#{directive.name}#{print_args(directive)} "\ | ||
"on #{directive.locations.join(' | ')}" | ||
end | ||
end | ||
|
||
class ScalarPrinter | ||
def self.print(type) | ||
"scalar #{type.name}" | ||
|
@@ -137,8 +178,9 @@ def self.print(type) | |
end | ||
|
||
class EnumPrinter | ||
extend DeprecatedPrinter | ||
def self.print(type) | ||
values = type.values.values.map{ |v| " #{v.name}" }.join("\n") | ||
values = type.values.values.map{ |v| " #{v.name}#{print_deprecated(v)}" }.join("\n") | ||
"enum #{type.name} {\n#{values}\n}" | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ | |
|
||
value "FOO" | ||
value "BAR" | ||
value "BAZ", deprecation_reason: 'Use "BAR".' | ||
value "WOZ", deprecation_reason: GraphQL::Directive::DEFAULT_DEPRECATION_REASON | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
end | ||
|
||
sub_input_type = GraphQL::InputObjectType.define do | ||
|
@@ -46,6 +48,7 @@ | |
field :title, !types.String | ||
field :body, !types.String | ||
field :comments, types[!comment_type] | ||
field :comments_count, !types.Int, deprecation_reason: 'Use "comments".' | ||
end | ||
|
||
query_root = GraphQL::ObjectType.define do | ||
|
@@ -70,24 +73,32 @@ | |
query: Query | ||
} | ||
|
||
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT | ||
|
||
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT | ||
|
||
directive @deprecated(reason: String! = \"No longer supported\") on FIELD_DEFINITION | ENUM_VALUE | ||
|
||
type __Directive { | ||
name: String! | ||
description: String | ||
args: [__InputValue!]! | ||
locations: [__DirectiveLocation!]! | ||
onOperation: Boolean! | ||
onFragment: Boolean! | ||
onField: Boolean! | ||
onOperation: Boolean! @deprecated(reason: \"Moved to 'locations' field\") | ||
onFragment: Boolean! @deprecated(reason: \"Moved to 'locations' field\") | ||
onField: Boolean! @deprecated(reason: \"Moved to 'locations' field\") | ||
} | ||
|
||
enum __DirectiveLocation { | ||
QUERY | ||
MUTATION | ||
SUBSCRIPTION | ||
FIELD | ||
FIELD_DEFINITION | ||
FRAGMENT_DEFINITION | ||
FRAGMENT_SPREAD | ||
INLINE_FRAGMENT | ||
ENUM_VALUE | ||
} | ||
|
||
type __EnumValue { | ||
|
@@ -158,6 +169,8 @@ | |
enum Choice { | ||
FOO | ||
BAR | ||
BAZ @deprecated(reason: "Use \\\"BAR\\\".") | ||
WOZ @deprecated | ||
} | ||
|
||
type Comment implements Node { | ||
|
@@ -173,6 +186,7 @@ | |
title: String! | ||
body: String! | ||
comments: [Comment!] | ||
comments_count: Int! @deprecated(reason: \"Use \\\"comments\\\".\") | ||
} | ||
|
||
type Query { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to figure out how this compares to the JS implementation, are there ever cases that we don't want to print these out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question.
I ported this from the JS implementation: https://github.com/graphql/graphql-js/blob/5e3d377e6e0881191efe02b9d40720ff0f0b5b0f/src/utilities/schemaPrinter.js#L28-L44
My understanding of the JS code is that when printing the schema, we do not want to include spec directives since they are part of the spec. We still print non-spec directives though.
When printing the introspection query, we only include spec directives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of me wants to leave this out, since there aren't any non-spec directives yet. But ... if we left it out, I can guarantee people would be surprised not to see
@defer
/@stream
in the query dump if I ever got around to merging that 😬There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure we're on the same page. You'd still see
@defer
and@stream
on the fields, you just don't see their definitions, i.e.directive @defer
.I think the other reason they do this in the JS implementation is because
directives
can be specified on theschema
: https://github.com/graphql/graphql-js/blob/a725499b155285c2e33647a93393c82689b20b0f/src/type/schema.js#L50-L54We don't support that yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, maybe I don't understand! If graphql-ruby supports
@stream
and@defer
, but they aren't part of the GraphQL spec, would they show up in a schema print-out or not?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't worry, I kept confusing myself here too.
There's two concepts:
Directive
andDirectiveDefinition
.Example
@skip
Directive
:Example
@skip
DirectiveDefinition
:The
is_spec_directive
method is used to omit theDirectiveDefinition
of spec directives from getting printed when dumping the schema.I need to rename
def print_directive(directive)
todef print_directive_definition(directive)
as that's not named correctly and could be a source of confusion.In terms of
Directive
we're only printing thedeprecated
directive at the moment, seeDeprecatedPrinter
. We may want to generalize this in the future though.Edit: So to answer your question about
@stream
and@defer
, if we don't add them tois_spec_directive
their definitions will get printed, but if you useGraphQL::Language::Generation
to print adocument
representing a query that uses@defer
on some selection set, it won't show up unless we print it inGraphQL::Language::Generation
.