graphql 1.7.14 → 1.8.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. checksums.yaml +5 -5
  2. data/lib/generators/graphql/function_generator.rb +1 -1
  3. data/lib/generators/graphql/loader_generator.rb +1 -1
  4. data/lib/generators/graphql/mutation_generator.rb +1 -6
  5. data/lib/generators/graphql/templates/function.erb +2 -2
  6. data/lib/generators/graphql/templates/loader.erb +2 -2
  7. data/lib/graphql.rb +2 -0
  8. data/lib/graphql/argument.rb +0 -1
  9. data/lib/graphql/backwards_compatibility.rb +2 -3
  10. data/lib/graphql/base_type.rb +18 -16
  11. data/lib/graphql/compatibility/query_parser_specification.rb +0 -117
  12. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -14
  13. data/lib/graphql/define/assign_object_field.rb +5 -12
  14. data/lib/graphql/deprecated_dsl.rb +28 -0
  15. data/lib/graphql/directive.rb +0 -1
  16. data/lib/graphql/enum_type.rb +1 -3
  17. data/lib/graphql/execution.rb +0 -1
  18. data/lib/graphql/execution/multiplex.rb +29 -12
  19. data/lib/graphql/field.rb +5 -20
  20. data/lib/graphql/function.rb +12 -0
  21. data/lib/graphql/input_object_type.rb +1 -3
  22. data/lib/graphql/internal_representation/node.rb +14 -26
  23. data/lib/graphql/internal_representation/visit.rb +6 -3
  24. data/lib/graphql/introspection/arguments_field.rb +0 -1
  25. data/lib/graphql/introspection/enum_values_field.rb +0 -1
  26. data/lib/graphql/introspection/fields_field.rb +0 -1
  27. data/lib/graphql/introspection/input_fields_field.rb +0 -1
  28. data/lib/graphql/introspection/interfaces_field.rb +0 -1
  29. data/lib/graphql/introspection/of_type_field.rb +0 -1
  30. data/lib/graphql/introspection/possible_types_field.rb +0 -1
  31. data/lib/graphql/introspection/schema_field.rb +0 -1
  32. data/lib/graphql/introspection/type_by_name_field.rb +0 -1
  33. data/lib/graphql/introspection/typename_field.rb +0 -1
  34. data/lib/graphql/language.rb +0 -3
  35. data/lib/graphql/language/generation.rb +182 -3
  36. data/lib/graphql/language/lexer.rb +69 -144
  37. data/lib/graphql/language/lexer.rl +4 -15
  38. data/lib/graphql/language/nodes.rb +76 -136
  39. data/lib/graphql/language/parser.rb +621 -668
  40. data/lib/graphql/language/parser.y +11 -17
  41. data/lib/graphql/language/token.rb +3 -10
  42. data/lib/graphql/object_type.rb +6 -1
  43. data/lib/graphql/query.rb +13 -8
  44. data/lib/graphql/query/arguments.rb +33 -48
  45. data/lib/graphql/query/context.rb +1 -0
  46. data/lib/graphql/query/literal_input.rb +1 -4
  47. data/lib/graphql/relay/connection_resolve.rb +3 -0
  48. data/lib/graphql/relay/global_id_resolve.rb +5 -1
  49. data/lib/graphql/relay/relation_connection.rb +19 -14
  50. data/lib/graphql/schema.rb +219 -12
  51. data/lib/graphql/schema/argument.rb +33 -0
  52. data/lib/graphql/schema/build_from_definition.rb +18 -64
  53. data/lib/graphql/schema/enum.rb +76 -0
  54. data/lib/graphql/schema/field.rb +127 -0
  55. data/lib/graphql/schema/field/dynamic_resolve.rb +63 -0
  56. data/lib/graphql/schema/field/unwrapped_resolve.rb +20 -0
  57. data/lib/graphql/schema/input_object.rb +61 -0
  58. data/lib/graphql/schema/interface.rb +32 -0
  59. data/lib/graphql/schema/loader.rb +2 -2
  60. data/lib/graphql/schema/member.rb +97 -0
  61. data/lib/graphql/schema/member/build_type.rb +106 -0
  62. data/lib/graphql/schema/member/has_fields.rb +56 -0
  63. data/lib/graphql/schema/member/instrumentation.rb +113 -0
  64. data/lib/graphql/schema/member/list_type_proxy.rb +21 -0
  65. data/lib/graphql/schema/member/non_null_type_proxy.rb +21 -0
  66. data/lib/graphql/schema/object.rb +65 -0
  67. data/lib/graphql/schema/printer.rb +266 -33
  68. data/lib/graphql/schema/scalar.rb +25 -0
  69. data/lib/graphql/schema/traversal.rb +26 -17
  70. data/lib/graphql/schema/union.rb +48 -0
  71. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -5
  72. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -15
  73. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +1 -11
  74. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -7
  75. data/lib/graphql/tracing.rb +0 -1
  76. data/lib/graphql/tracing/platform_tracing.rb +7 -20
  77. data/lib/graphql/tracing/scout_tracing.rb +2 -2
  78. data/lib/graphql/unresolved_type_error.rb +2 -3
  79. data/lib/graphql/version.rb +1 -1
  80. data/readme.md +1 -1
  81. data/spec/dummy/app/channels/graphql_channel.rb +1 -22
  82. data/spec/dummy/log/development.log +0 -239
  83. data/spec/dummy/log/test.log +0 -204
  84. data/spec/dummy/test/system/action_cable_subscription_test.rb +0 -4
  85. data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
  86. data/spec/generators/graphql/function_generator_spec.rb +0 -26
  87. data/spec/generators/graphql/loader_generator_spec.rb +0 -24
  88. data/spec/graphql/analysis/max_query_complexity_spec.rb +3 -3
  89. data/spec/graphql/analysis/max_query_depth_spec.rb +3 -3
  90. data/spec/graphql/backtrace_spec.rb +0 -10
  91. data/spec/graphql/base_type_spec.rb +5 -19
  92. data/spec/graphql/boolean_type_spec.rb +3 -3
  93. data/spec/graphql/directive_spec.rb +1 -3
  94. data/spec/graphql/enum_type_spec.rb +5 -18
  95. data/spec/graphql/execution/execute_spec.rb +1 -1
  96. data/spec/graphql/execution/multiplex_spec.rb +2 -2
  97. data/spec/graphql/float_type_spec.rb +2 -2
  98. data/spec/graphql/id_type_spec.rb +1 -1
  99. data/spec/graphql/input_object_type_spec.rb +2 -15
  100. data/spec/graphql/int_type_spec.rb +2 -2
  101. data/spec/graphql/internal_representation/rewrite_spec.rb +2 -2
  102. data/spec/graphql/introspection/schema_type_spec.rb +0 -1
  103. data/spec/graphql/language/generation_spec.rb +186 -21
  104. data/spec/graphql/language/lexer_spec.rb +1 -21
  105. data/spec/graphql/language/nodes_spec.rb +12 -21
  106. data/spec/graphql/language/parser_spec.rb +1 -1
  107. data/spec/graphql/query/arguments_spec.rb +15 -37
  108. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +2 -2
  109. data/spec/graphql/query/variables_spec.rb +1 -1
  110. data/spec/graphql/query_spec.rb +5 -31
  111. data/spec/graphql/rake_task_spec.rb +1 -3
  112. data/spec/graphql/relay/base_connection_spec.rb +1 -1
  113. data/spec/graphql/relay/connection_instrumentation_spec.rb +2 -2
  114. data/spec/graphql/relay/connection_resolve_spec.rb +1 -1
  115. data/spec/graphql/relay/connection_type_spec.rb +1 -1
  116. data/spec/graphql/relay/mutation_spec.rb +3 -3
  117. data/spec/graphql/relay/relation_connection_spec.rb +1 -65
  118. data/spec/graphql/schema/build_from_definition_spec.rb +4 -86
  119. data/spec/graphql/schema/enum_spec.rb +60 -0
  120. data/spec/graphql/schema/field_spec.rb +14 -0
  121. data/spec/graphql/schema/input_object_spec.rb +43 -0
  122. data/spec/graphql/schema/interface_spec.rb +98 -0
  123. data/spec/graphql/schema/object_spec.rb +119 -0
  124. data/spec/graphql/schema/printer_spec.rb +15 -92
  125. data/spec/graphql/schema/scalar_spec.rb +40 -0
  126. data/spec/graphql/schema/union_spec.rb +35 -0
  127. data/spec/graphql/schema/validation_spec.rb +1 -1
  128. data/spec/graphql/schema/warden_spec.rb +11 -11
  129. data/spec/graphql/schema_spec.rb +25 -23
  130. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +2 -10
  131. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +2 -2
  132. data/spec/graphql/string_type_spec.rb +3 -3
  133. data/spec/graphql/subscriptions_spec.rb +1 -1
  134. data/spec/graphql/tracing/platform_tracing_spec.rb +1 -60
  135. data/spec/support/dummy/schema.rb +25 -39
  136. data/spec/support/jazz.rb +334 -0
  137. data/spec/support/lazy_helpers.rb +21 -23
  138. data/spec/support/star_wars/data.rb +7 -6
  139. data/spec/support/star_wars/schema.rb +109 -142
  140. metadata +39 -33
  141. data/lib/graphql/execution/instrumentation.rb +0 -82
  142. data/lib/graphql/language/block_string.rb +0 -47
  143. data/lib/graphql/language/document_from_schema_definition.rb +0 -277
  144. data/lib/graphql/language/printer.rb +0 -351
  145. data/lib/graphql/tracing/data_dog_tracing.rb +0 -49
  146. data/spec/graphql/execution/instrumentation_spec.rb +0 -165
  147. data/spec/graphql/language/block_string_spec.rb +0 -70
  148. data/spec/graphql/language/document_from_schema_definition_spec.rb +0 -770
  149. data/spec/graphql/language/printer_spec.rb +0 -203
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ # @api private
6
+ module BuildType
7
+ module_function
8
+ # @param type_expr [String, Class, GraphQL::BaseType]
9
+ # @return [GraphQL::BaseType]
10
+ def parse_type(type_expr, null:)
11
+ list_type = false
12
+
13
+ return_type = case type_expr
14
+ when String
15
+ case type_expr
16
+ when "String"
17
+ GraphQL::STRING_TYPE
18
+ when "Int", "Integer"
19
+ GraphQL::INT_TYPE
20
+ when "Float"
21
+ GraphQL::FLOAT_TYPE
22
+ when "Boolean"
23
+ GraphQL::BOOLEAN_TYPE
24
+ when "ID"
25
+ GraphQL::ID_TYPE
26
+ when /\A\[.*\]\Z/
27
+ list_type = true
28
+ # List members are required by default
29
+ parse_type(type_expr[1..-2], null: false)
30
+ when /.*!\Z/
31
+ null = false
32
+ parse_type(type_expr[0..-2], null: true)
33
+ else
34
+ maybe_type = Object.const_get(type_expr)
35
+ case maybe_type
36
+ when GraphQL::BaseType
37
+ maybe_type
38
+ when Class
39
+ if maybe_type < GraphQL::Schema::Member
40
+ maybe_type.graphql_definition
41
+ else
42
+ raise "Unexpected class found for GraphQL type: #{type_expr} (must be GraphQL::Object)"
43
+ end
44
+ end
45
+ end
46
+ when GraphQL::BaseType
47
+ type_expr
48
+ when Array
49
+ if type_expr.length != 1
50
+ raise "Use an array of length = 1 for list types; other arrays are not supported"
51
+ end
52
+ list_type = true
53
+ # List members are required by default
54
+ parse_type(type_expr.first, null: false)
55
+ when Class
56
+ if Class < GraphQL::Schema::Member
57
+ type_expr.graphql_definition
58
+ else
59
+ # Eg `String` => GraphQL::STRING_TYPE
60
+ parse_type(type_expr.name, null: true)
61
+ end
62
+ end
63
+
64
+ # Apply list_type first, that way the
65
+ # .to_non_null_type applies to the list type, not the inner type
66
+ if list_type
67
+ return_type = return_type.to_list_type
68
+ end
69
+
70
+ if !null
71
+ return_type = return_type.to_non_null_type
72
+ end
73
+
74
+
75
+ return_type
76
+ end
77
+
78
+ def to_type_name(something)
79
+ case something
80
+ when GraphQL::BaseType
81
+ something.unwrap.name
82
+ when Array
83
+ to_type_name(something.first)
84
+ when Module
85
+ if something < GraphQL::Schema::Member
86
+ something.graphql_name
87
+ else
88
+ something.name.split("::").last
89
+ end
90
+ when String
91
+ something.gsub(/\]\[\!/, "").split("::").last
92
+ else
93
+ raise "Unhandled to_type_name input: #{something} (#{something.class})"
94
+ end
95
+ end
96
+
97
+ def underscore(string)
98
+ string
99
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
100
+ .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
101
+ .downcase
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ # Shared code for Object and Interface
6
+ module HasFields
7
+ # Add a field to this object or interface with the given definition
8
+ # @see {GraphQL::Schema::Field#initialize} for method signature
9
+ # @return [void]
10
+ def field(*args, &block)
11
+ field_defn = field_class.new(*args, &block)
12
+ add_field(field_defn)
13
+ nil
14
+ end
15
+
16
+ # @return [Array<GraphQL::Schema::Field>] Fields on this object, including inherited fields
17
+ def fields
18
+ all_fields = own_fields
19
+ inherited_fields = (superclass.is_a?(HasFields) ? superclass.fields : [])
20
+ # Remove any inherited fields which were overridden on this class:
21
+ inherited_fields.each do |inherited_f|
22
+ if all_fields.none? {|f| f.name == inherited_f.name}
23
+ all_fields << inherited_f
24
+ end
25
+ end
26
+ all_fields
27
+ end
28
+
29
+ # Register this field with the class, overriding a previous one if needed
30
+ # @param field_defn [GraphQL::Schema::Field]
31
+ # @return [void]
32
+ def add_field(field_defn)
33
+ fields.reject! {|f| f.name == field_defn.name}
34
+ own_fields << field_defn
35
+ nil
36
+ end
37
+
38
+ # @return [Class] The class to initialize when adding fields to this kind of schema member
39
+ def field_class(new_field_class = nil)
40
+ if new_field_class
41
+ @field_class = new_field_class
42
+ else
43
+ @field_class || superclass.field_class
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # @return [Array<GraphQL::Schema::Field>] Fields defined on this class _specifically_, not parent classes
50
+ def own_fields
51
+ @own_fields ||= []
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+ # test_via: ../object.rb
3
+
4
+ module GraphQL
5
+ class Schema
6
+ class Member
7
+ module Instrumentation
8
+ module_function
9
+ def instrument(type, field)
10
+ return_type = field.type.unwrap
11
+ if return_type.metadata[:object_class] ||
12
+ return_type.is_a?(GraphQL::InterfaceType) ||
13
+ (return_type.is_a?(GraphQL::UnionType) && return_type.possible_types.any? { |t| t.metadata[:object_class] })
14
+ field = apply_proxy(field)
15
+ end
16
+
17
+ field
18
+ end
19
+
20
+ def before_query(query)
21
+ # Get the root type for this query
22
+ root_node = query.irep_selection
23
+ if root_node.nil?
24
+ # It's an invalid query, nothing to do here
25
+ else
26
+ root_type = query.irep_selection.return_type
27
+ # If it has a wrapper, apply it
28
+ wrapper_class = root_type.metadata[:object_class]
29
+ if wrapper_class
30
+ new_root_value = wrapper_class.new(query.root_value, query.context)
31
+ query.root_value = new_root_value
32
+ end
33
+ end
34
+ end
35
+
36
+ def after_query(_query)
37
+ end
38
+
39
+ private
40
+
41
+ module_function
42
+
43
+ def apply_proxy(field)
44
+ resolve_proc = field.resolve_proc
45
+ lazy_resolve_proc = field.lazy_resolve_proc
46
+ inner_return_type = field.type.unwrap
47
+ depth = list_depth(field.type)
48
+
49
+ field.redefine(
50
+ resolve: ProxiedResolve.new(inner_resolve: resolve_proc, list_depth: depth, inner_return_type: inner_return_type),
51
+ lazy_resolve: ProxiedResolve.new(inner_resolve: lazy_resolve_proc, list_depth: depth, inner_return_type: inner_return_type),
52
+ )
53
+ end
54
+
55
+ def list_depth(type, starting_at = 0)
56
+ case type
57
+ when GraphQL::ListType
58
+ list_depth(type.of_type, starting_at + 1)
59
+ when GraphQL::NonNullType
60
+ list_depth(type.of_type, starting_at)
61
+ else
62
+ starting_at
63
+ end
64
+ end
65
+
66
+ class ProxiedResolve
67
+ def initialize(inner_resolve:, list_depth:, inner_return_type:)
68
+ @inner_resolve = inner_resolve
69
+ @inner_return_type = inner_return_type
70
+ @list_depth = list_depth
71
+ end
72
+
73
+ def call(obj, args, ctx)
74
+ result = @inner_resolve.call(obj, args, ctx)
75
+ if ctx.schema.lazy?(result)
76
+ # Wrap it later
77
+ result
78
+ elsif ctx.skip == result
79
+ result
80
+ else
81
+ proxy_to_depth(result, @list_depth, @inner_return_type, ctx)
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def proxy_to_depth(obj, depth, type, ctx)
88
+ if depth > 0
89
+ obj.map { |inner_obj| proxy_to_depth(inner_obj, depth - 1, type, ctx) }
90
+ elsif obj.nil?
91
+ obj
92
+ else
93
+ concrete_type = case type
94
+ when GraphQL::UnionType, GraphQL::InterfaceType
95
+ ctx.query.resolve_type(type, obj)
96
+ when GraphQL::ObjectType
97
+ type
98
+ else
99
+ raise "unexpected proxying type #{type} for #{obj} at #{ctx.owner_type}.#{ctx.field.name}"
100
+ end
101
+
102
+ if concrete_type && (object_class = concrete_type.metadata[:object_class])
103
+ object_class.new(obj, ctx)
104
+ else
105
+ obj
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ # Wraps a {Schema::Member} as a list type.
6
+ # @see {Schema::Member#to_list_type}
7
+ # @api private
8
+ class ListTypeProxy
9
+ include GraphQL::Schema::Member::CachedGraphQLDefinition
10
+
11
+ def initialize(member)
12
+ @member = member
13
+ end
14
+
15
+ def to_graphql
16
+ @member.graphql_definition.to_list_type
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ # Wraps a {Schema::Member} when it is required.
6
+ # @see {Schema::Member#to_non_null_type}
7
+ # @api private
8
+ class NonNullTypeProxy
9
+ include GraphQL::Schema::Member::CachedGraphQLDefinition
10
+
11
+ def initialize(member)
12
+ @member = member
13
+ end
14
+
15
+ def to_graphql
16
+ @member.graphql_definition.to_non_null_type
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Object < GraphQL::Schema::Member
6
+ attr_reader :object
7
+
8
+ def initialize(object, context)
9
+ @object = object
10
+ @context = context
11
+ end
12
+ extend GraphQL::Schema::Member::HasFields
13
+ field_class GraphQL::Schema::Field
14
+
15
+ class << self
16
+ def implements(*new_interfaces)
17
+ new_interfaces.each do |int|
18
+ if int.is_a?(Class) && int < GraphQL::Schema::Interface
19
+ # Add the graphql field defns
20
+ int.fields.each do |field|
21
+ add_field(field)
22
+ end
23
+ # And call the implemented hook
24
+ int.apply_implemented(self)
25
+ else
26
+ int.all_fields.each do |f|
27
+ field(f.name, field: f)
28
+ end
29
+ end
30
+ end
31
+ own_interfaces.concat(new_interfaces)
32
+ end
33
+
34
+ def interfaces
35
+ own_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces : [])
36
+ end
37
+
38
+ def own_interfaces
39
+ @own_interfaces ||= []
40
+ end
41
+
42
+ # @return [GraphQL::ObjectType]
43
+ def to_graphql
44
+ obj_type = GraphQL::ObjectType.new
45
+ obj_type.name = graphql_name
46
+ obj_type.description = description
47
+ obj_type.interfaces = interfaces
48
+
49
+ fields.each do |field_inst|
50
+ field_defn = field_inst.graphql_definition
51
+ obj_type.fields[field_defn.name] = field_defn
52
+ end
53
+
54
+ obj_type.metadata[:object_class] = self
55
+
56
+ obj_type
57
+ end
58
+
59
+ def global_id_field(field_name)
60
+ field field_name, "ID", null: false, resolve: GraphQL::Relay::GlobalIdResolve.new(type: self)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -36,7 +36,7 @@ module GraphQL
36
36
  # printer = GraphQL::Schema::Printer.new(MySchema)
37
37
  # puts printer.print_type(post_type)
38
38
  #
39
- class Printer < GraphQL::Language::Printer
39
+ class Printer
40
40
  attr_reader :schema, :warden
41
41
 
42
42
  # @param schema [GraphQL::Schema]
@@ -45,32 +45,21 @@ module GraphQL
45
45
  # @param except [<#call(member, ctx)>]
46
46
  # @param introspection [Boolean] Should include the introspection types in the string?
47
47
  def initialize(schema, context: nil, only: nil, except: nil, introspection: false)
48
- @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new(
49
- schema,
50
- context: context,
51
- only: only,
52
- except: except,
53
- include_introspection_types: introspection,
54
- )
55
-
56
- @document = @document_from_schema.document
57
-
58
48
  @schema = schema
49
+ @context = context
50
+
51
+ blacklist = build_blacklist(only, except, introspection: introspection)
52
+ filter = GraphQL::Filter.new(except: blacklist)
53
+ @warden = GraphQL::Schema::Warden.new(filter, schema: @schema, context: @context)
59
54
  end
60
55
 
61
56
  # Return the GraphQL schema string for the introspection type system
62
57
  def self.print_introspection_schema
63
58
  query_root = ObjectType.define(name: "Root")
64
59
  schema = GraphQL::Schema.define(query: query_root)
65
-
66
- introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
67
- schema,
68
- except: ->(member, _) { member.name == "Root" },
69
- include_introspection_types: true,
70
- include_built_in_directives: true,
71
- ).document
72
-
73
- introspection_schema_ast.to_query_string(printer: IntrospectionPrinter.new)
60
+ blacklist = ->(m, ctx) { m == query_root }
61
+ printer = new(schema, except: blacklist, introspection: true)
62
+ printer.print_schema
74
63
  end
75
64
 
76
65
  # Return a GraphQL schema string for the defined types in the schema
@@ -85,33 +74,277 @@ module GraphQL
85
74
 
86
75
  # Return a GraphQL schema string for the defined types in the schema
87
76
  def print_schema
88
- print(@document)
77
+ directive_definitions = warden.directives.map { |directive| print_directive(directive) }
78
+
79
+ printable_types = warden.types.reject(&:default_scalar?)
80
+
81
+ type_definitions = printable_types
82
+ .sort_by(&:name)
83
+ .map { |type| print_type(type) }
84
+
85
+ [print_schema_definition].compact
86
+ .concat(directive_definitions)
87
+ .concat(type_definitions).join("\n\n")
89
88
  end
90
89
 
91
90
  def print_type(type)
92
- node = @document_from_schema.build_object_type_node(type)
93
- print(node)
91
+ TypeKindPrinters::STRATEGIES.fetch(type.kind).print(warden, type)
94
92
  end
95
93
 
96
- def print_directive(directive)
97
- if directive.name == "deprecated"
98
- reason = directive.arguments.find { |arg| arg.name == "reason" }
94
+ private
95
+
96
+ # By default, these are included in a schema printout
97
+ IS_USER_DEFINED_MEMBER = ->(member) {
98
+ case member
99
+ when GraphQL::BaseType
100
+ !member.introspection?
101
+ when GraphQL::Directive
102
+ !member.default_directive?
103
+ else
104
+ true
105
+ end
106
+ }
107
+
108
+ private_constant :IS_USER_DEFINED_MEMBER
99
109
 
100
- if reason.value == GraphQL::Directive::DEFAULT_DEPRECATION_REASON
101
- "@deprecated"
110
+ def build_blacklist(only, except, introspection:)
111
+ if introspection
112
+ if only
113
+ ->(m, ctx) { !only.call(m, ctx) }
114
+ elsif except
115
+ except
102
116
  else
103
- "@deprecated(reason: #{reason.value.to_s.inspect})"
117
+ ->(m, ctx) { false }
104
118
  end
105
119
  else
106
- super
120
+ if only
121
+ ->(m, ctx) { !(IS_USER_DEFINED_MEMBER.call(m) && only.call(m, ctx)) }
122
+ elsif except
123
+ ->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) || except.call(m, ctx) }
124
+ else
125
+ ->(m, ctx) { !IS_USER_DEFINED_MEMBER.call(m) }
126
+ end
107
127
  end
108
128
  end
109
129
 
110
- class IntrospectionPrinter < GraphQL::Language::Printer
111
- def print_schema_definition(schema)
112
- "schema {\n query: Root\n}"
130
+ def print_schema_definition
131
+ if (schema.query.nil? || schema.query.name == 'Query') &&
132
+ (schema.mutation.nil? || schema.mutation.name == 'Mutation') &&
133
+ (schema.subscription.nil? || schema.subscription.name == 'Subscription')
134
+ return
113
135
  end
136
+
137
+ operations = [:query, :mutation, :subscription].map do |operation_type|
138
+ object_type = schema.public_send(operation_type)
139
+ # Special treatment for the introspection schema, which prints `{ query: "Root" }`
140
+ if object_type && (warden.get_type(object_type.name) || (object_type.name == "Root" && schema.query == object_type))
141
+ " #{operation_type}: #{object_type.name}\n"
142
+ else
143
+ nil
144
+ end
145
+ end.compact.join
146
+ "schema {\n#{operations}}"
147
+ end
148
+
149
+ def print_directive(directive)
150
+ TypeKindPrinters::DirectivePrinter.print(warden, directive)
151
+ end
152
+
153
+ module TypeKindPrinters
154
+ module DeprecatedPrinter
155
+ def print_deprecated(field_or_enum_value)
156
+ return unless field_or_enum_value.deprecation_reason
157
+
158
+ case field_or_enum_value.deprecation_reason
159
+ when nil
160
+ ''
161
+ when '', GraphQL::Directive::DEFAULT_DEPRECATION_REASON
162
+ ' @deprecated'
163
+ else
164
+ " @deprecated(reason: #{field_or_enum_value.deprecation_reason.to_s.inspect})"
165
+ end
166
+ end
167
+ end
168
+
169
+ module DescriptionPrinter
170
+ def print_description(definition, indentation='', first_in_block=true)
171
+ return '' unless definition.description
172
+
173
+ description = indentation != '' && !first_in_block ? "\n".dup : "".dup
174
+ description << GraphQL::Language::Comments.commentize(definition.description, indent: indentation)
175
+ end
176
+ end
177
+
178
+ module ArgsPrinter
179
+ include DescriptionPrinter
180
+ def print_args(warden, field, indentation = '')
181
+ arguments = warden.arguments(field)
182
+ return if arguments.empty?
183
+
184
+ if arguments.all?{ |arg| !arg.description }
185
+ return "(#{arguments.map{ |arg| print_input_value(arg) }.join(", ")})"
186
+ end
187
+
188
+ out = "(\n".dup
189
+ out << arguments.sort_by(&:name).map.with_index{ |arg, i|
190
+ "#{print_description(arg, " #{indentation}", i == 0)} #{indentation}"\
191
+ "#{print_input_value(arg)}"
192
+ }.join("\n")
193
+ out << "\n#{indentation})"
194
+ end
195
+
196
+ def print_input_value(arg)
197
+ if arg.default_value?
198
+ default_string = " = #{print_value(arg.default_value, arg.type)}"
199
+ else
200
+ default_string = nil
201
+ end
202
+
203
+ "#{arg.name}: #{arg.type.to_s}#{default_string}"
204
+ end
205
+
206
+ def print_value(value, type)
207
+ case type
208
+ when FLOAT_TYPE
209
+ return 'null' if value.nil?
210
+ value.to_f.inspect
211
+ when INT_TYPE
212
+ return 'null' if value.nil?
213
+ value.to_i.inspect
214
+ when BOOLEAN_TYPE
215
+ return 'null' if value.nil?
216
+ (!!value).inspect
217
+ when ScalarType, ID_TYPE, STRING_TYPE
218
+ return 'null' if value.nil?
219
+ value.to_s.inspect
220
+ when EnumType
221
+ return 'null' if value.nil?
222
+ type.coerce_isolated_result(value)
223
+ when InputObjectType
224
+ return 'null' if value.nil?
225
+ fields = value.to_h.map{ |field_name, field_value|
226
+ field_type = type.input_fields.fetch(field_name.to_s).type
227
+ "#{field_name}: #{print_value(field_value, field_type)}"
228
+ }.join(", ")
229
+ "{#{fields}}"
230
+ when NonNullType
231
+ print_value(value, type.of_type)
232
+ when ListType
233
+ return 'null' if value.nil?
234
+ "[#{value.to_a.map{ |v| print_value(v, type.of_type) }.join(", ")}]"
235
+ else
236
+ raise NotImplementedError, "Unexpected value type #{type.inspect}"
237
+ end
238
+ end
239
+ end
240
+
241
+ module FieldPrinter
242
+ include DeprecatedPrinter
243
+ include ArgsPrinter
244
+ include DescriptionPrinter
245
+ def print_fields(warden, type)
246
+ fields = warden.fields(type)
247
+ fields.sort_by(&:name).map.with_index { |field, i|
248
+ "#{print_description(field, ' ', i == 0)}"\
249
+ " #{field.name}#{print_args(warden, field, ' ')}: #{field.type}#{print_deprecated(field)}"
250
+ }.join("\n")
251
+ end
252
+ end
253
+
254
+ class DirectivePrinter
255
+ extend ArgsPrinter
256
+ extend DescriptionPrinter
257
+ def self.print(warden, directive)
258
+ "#{print_description(directive)}"\
259
+ "directive @#{directive.name}#{print_args(warden, directive)} "\
260
+ "on #{directive.locations.join(' | ')}"
261
+ end
262
+ end
263
+
264
+ class ScalarPrinter
265
+ extend DescriptionPrinter
266
+ def self.print(warden, type)
267
+ "#{print_description(type)}"\
268
+ "scalar #{type.name}"
269
+ end
270
+ end
271
+
272
+ class ObjectPrinter
273
+ extend FieldPrinter
274
+ extend DescriptionPrinter
275
+ def self.print(warden, type)
276
+ interfaces = warden.interfaces(type)
277
+ if interfaces.any?
278
+ implementations = " implements #{interfaces.sort_by(&:name).map(&:to_s).join(", ")}"
279
+ else
280
+ implementations = nil
281
+ end
282
+
283
+ "#{print_description(type)}"\
284
+ "type #{type.name}#{implementations} {\n"\
285
+ "#{print_fields(warden, type)}\n"\
286
+ "}"
287
+ end
288
+ end
289
+
290
+ class InterfacePrinter
291
+ extend FieldPrinter
292
+ extend DescriptionPrinter
293
+ def self.print(warden, type)
294
+ "#{print_description(type)}"\
295
+ "interface #{type.name} {\n#{print_fields(warden, type)}\n}"
296
+ end
297
+ end
298
+
299
+ class UnionPrinter
300
+ extend DescriptionPrinter
301
+ def self.print(warden, type)
302
+ possible_types = warden.possible_types(type)
303
+ "#{print_description(type)}"\
304
+ "union #{type.name} = #{possible_types.sort_by(&:name).map(&:to_s).join(" | ")}"
305
+ end
306
+ end
307
+
308
+ class EnumPrinter
309
+ extend DeprecatedPrinter
310
+ extend DescriptionPrinter
311
+ def self.print(warden, type)
312
+ enum_values = warden.enum_values(type)
313
+
314
+ values = enum_values.sort_by(&:name).map.with_index { |v, i|
315
+ "#{print_description(v, ' ', i == 0)}"\
316
+ " #{v.name}#{print_deprecated(v)}"
317
+ }.join("\n")
318
+
319
+ "#{print_description(type)}"\
320
+ "enum #{type.name} {\n#{values}\n}"
321
+ end
322
+ end
323
+
324
+ class InputObjectPrinter
325
+ extend FieldPrinter
326
+ extend DescriptionPrinter
327
+ def self.print(warden, type)
328
+ arguments = warden.arguments(type)
329
+ fields = arguments.sort_by(&:name).map.with_index{ |field, i|
330
+ "#{print_description(field, " ", i == 0)}"\
331
+ " #{print_input_value(field)}"
332
+ }.join("\n")
333
+ "#{print_description(type)}"\
334
+ "input #{type.name} {\n#{fields}\n}"
335
+ end
336
+ end
337
+
338
+ STRATEGIES = {
339
+ GraphQL::TypeKinds::SCALAR => ScalarPrinter,
340
+ GraphQL::TypeKinds::OBJECT => ObjectPrinter,
341
+ GraphQL::TypeKinds::INTERFACE => InterfacePrinter,
342
+ GraphQL::TypeKinds::UNION => UnionPrinter,
343
+ GraphQL::TypeKinds::ENUM => EnumPrinter,
344
+ GraphQL::TypeKinds::INPUT_OBJECT => InputObjectPrinter,
345
+ }
114
346
  end
347
+ private_constant :TypeKindPrinters
115
348
  end
116
349
  end
117
350
  end