graphql 1.7.14 → 1.8.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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