graphql 1.11.4 → 1.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/object_generator.rb +2 -0
  4. data/lib/generators/graphql/relay_generator.rb +63 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  7. data/lib/generators/graphql/templates/node_type.erb +9 -0
  8. data/lib/generators/graphql/templates/object.erb +1 -1
  9. data/lib/generators/graphql/templates/query_type.erb +1 -3
  10. data/lib/generators/graphql/templates/schema.erb +8 -35
  11. data/lib/generators/graphql/templates/union.erb +1 -1
  12. data/lib/graphql.rb +55 -4
  13. data/lib/graphql/analysis/analyze_query.rb +7 -0
  14. data/lib/graphql/analysis/ast.rb +11 -2
  15. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  16. data/lib/graphql/argument.rb +3 -3
  17. data/lib/graphql/backtrace.rb +28 -19
  18. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  19. data/lib/graphql/backtrace/table.rb +22 -2
  20. data/lib/graphql/backtrace/tracer.rb +40 -8
  21. data/lib/graphql/backwards_compatibility.rb +1 -0
  22. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  23. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  24. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  25. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  26. data/lib/graphql/dataloader.rb +198 -0
  27. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  28. data/lib/graphql/dataloader/request.rb +24 -0
  29. data/lib/graphql/dataloader/request_all.rb +22 -0
  30. data/lib/graphql/dataloader/source.rb +93 -0
  31. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  32. data/lib/graphql/define/instance_definable.rb +32 -2
  33. data/lib/graphql/define/type_definer.rb +5 -5
  34. data/lib/graphql/deprecated_dsl.rb +5 -0
  35. data/lib/graphql/enum_type.rb +2 -0
  36. data/lib/graphql/execution/errors.rb +4 -0
  37. data/lib/graphql/execution/execute.rb +7 -0
  38. data/lib/graphql/execution/interpreter.rb +20 -6
  39. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  40. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  41. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  42. data/lib/graphql/execution/interpreter/runtime.rb +236 -120
  43. data/lib/graphql/execution/multiplex.rb +20 -6
  44. data/lib/graphql/function.rb +4 -0
  45. data/lib/graphql/input_object_type.rb +2 -0
  46. data/lib/graphql/integer_decoding_error.rb +17 -0
  47. data/lib/graphql/interface_type.rb +3 -1
  48. data/lib/graphql/introspection.rb +96 -0
  49. data/lib/graphql/introspection/field_type.rb +7 -3
  50. data/lib/graphql/introspection/input_value_type.rb +6 -0
  51. data/lib/graphql/introspection/introspection_query.rb +6 -92
  52. data/lib/graphql/introspection/type_type.rb +7 -3
  53. data/lib/graphql/invalid_null_error.rb +1 -1
  54. data/lib/graphql/language/block_string.rb +24 -5
  55. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  56. data/lib/graphql/language/lexer.rb +7 -3
  57. data/lib/graphql/language/lexer.rl +7 -3
  58. data/lib/graphql/language/nodes.rb +1 -1
  59. data/lib/graphql/language/parser.rb +107 -103
  60. data/lib/graphql/language/parser.y +4 -0
  61. data/lib/graphql/language/sanitized_printer.rb +59 -26
  62. data/lib/graphql/name_validator.rb +6 -7
  63. data/lib/graphql/object_type.rb +2 -0
  64. data/lib/graphql/pagination/connection.rb +5 -1
  65. data/lib/graphql/pagination/connections.rb +15 -17
  66. data/lib/graphql/query.rb +8 -3
  67. data/lib/graphql/query/context.rb +18 -3
  68. data/lib/graphql/query/serial_execution.rb +1 -0
  69. data/lib/graphql/query/validation_pipeline.rb +1 -1
  70. data/lib/graphql/relay/array_connection.rb +2 -2
  71. data/lib/graphql/relay/base_connection.rb +7 -0
  72. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  73. data/lib/graphql/relay/connection_type.rb +1 -1
  74. data/lib/graphql/relay/mutation.rb +1 -0
  75. data/lib/graphql/relay/node.rb +3 -0
  76. data/lib/graphql/relay/range_add.rb +14 -5
  77. data/lib/graphql/relay/type_extensions.rb +2 -0
  78. data/lib/graphql/scalar_type.rb +2 -0
  79. data/lib/graphql/schema.rb +104 -39
  80. data/lib/graphql/schema/argument.rb +74 -5
  81. data/lib/graphql/schema/build_from_definition.rb +203 -86
  82. data/lib/graphql/schema/default_type_error.rb +2 -0
  83. data/lib/graphql/schema/directive.rb +76 -0
  84. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  85. data/lib/graphql/schema/directive/flagged.rb +57 -0
  86. data/lib/graphql/schema/enum.rb +3 -0
  87. data/lib/graphql/schema/enum_value.rb +12 -6
  88. data/lib/graphql/schema/field.rb +59 -24
  89. data/lib/graphql/schema/field/connection_extension.rb +10 -8
  90. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  91. data/lib/graphql/schema/input_object.rb +38 -25
  92. data/lib/graphql/schema/interface.rb +2 -1
  93. data/lib/graphql/schema/late_bound_type.rb +2 -2
  94. data/lib/graphql/schema/loader.rb +1 -0
  95. data/lib/graphql/schema/member.rb +4 -0
  96. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  97. data/lib/graphql/schema/member/build_type.rb +17 -7
  98. data/lib/graphql/schema/member/has_arguments.rb +70 -51
  99. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  100. data/lib/graphql/schema/member/has_directives.rb +98 -0
  101. data/lib/graphql/schema/member/has_fields.rb +2 -2
  102. data/lib/graphql/schema/member/has_validators.rb +31 -0
  103. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  104. data/lib/graphql/schema/object.rb +11 -0
  105. data/lib/graphql/schema/printer.rb +5 -4
  106. data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
  107. data/lib/graphql/schema/resolver.rb +7 -0
  108. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  109. data/lib/graphql/schema/subscription.rb +19 -1
  110. data/lib/graphql/schema/timeout.rb +29 -15
  111. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  112. data/lib/graphql/schema/unique_within_type.rb +1 -2
  113. data/lib/graphql/schema/validation.rb +10 -0
  114. data/lib/graphql/schema/validator.rb +163 -0
  115. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  116. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  117. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  118. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  119. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  120. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  121. data/lib/graphql/static_validation.rb +1 -0
  122. data/lib/graphql/static_validation/all_rules.rb +1 -0
  123. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  124. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  125. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  126. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  127. data/lib/graphql/static_validation/validator.rb +33 -7
  128. data/lib/graphql/subscriptions.rb +18 -23
  129. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
  130. data/lib/graphql/tracing.rb +2 -2
  131. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  132. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  133. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  134. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  135. data/lib/graphql/types/int.rb +9 -2
  136. data/lib/graphql/types/relay.rb +11 -3
  137. data/lib/graphql/types/relay/base_connection.rb +2 -91
  138. data/lib/graphql/types/relay/base_edge.rb +2 -34
  139. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  140. data/lib/graphql/types/relay/default_relay.rb +27 -0
  141. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  142. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  143. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  144. data/lib/graphql/types/relay/node.rb +2 -4
  145. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  146. data/lib/graphql/types/relay/node_field.rb +1 -19
  147. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  148. data/lib/graphql/types/relay/page_info.rb +2 -14
  149. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  150. data/lib/graphql/types/string.rb +7 -1
  151. data/lib/graphql/unauthorized_error.rb +1 -1
  152. data/lib/graphql/union_type.rb +2 -0
  153. data/lib/graphql/upgrader/member.rb +1 -0
  154. data/lib/graphql/upgrader/schema.rb +1 -0
  155. data/lib/graphql/version.rb +1 -1
  156. data/readme.md +1 -1
  157. metadata +49 -6
  158. data/lib/graphql/types/relay/base_field.rb +0 -22
  159. data/lib/graphql/types/relay/base_interface.rb +0 -29
  160. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -29,11 +29,13 @@ module GraphQL
29
29
 
30
30
  include Tracing::Traceable
31
31
 
32
- attr_reader :context, :queries, :schema, :max_complexity
32
+ attr_reader :context, :queries, :schema, :max_complexity, :dataloader
33
33
  def initialize(schema:, queries:, context:, max_complexity:)
34
34
  @schema = schema
35
35
  @queries = queries
36
+ @queries.each { |q| q.multiplex = self }
36
37
  @context = context
38
+ @context[:dataloader] = @dataloader = @schema.dataloader_class.new(context)
37
39
  @tracers = schema.tracers + (context[:tracers] || [])
38
40
  # Support `context: {backtrace: true}`
39
41
  if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
@@ -79,20 +81,30 @@ module GraphQL
79
81
  multiplex.schema.query_execution_strategy.begin_multiplex(multiplex)
80
82
  queries = multiplex.queries
81
83
  # Do as much eager evaluation of the query as possible
82
- results = queries.map do |query|
83
- begin_query(query, multiplex)
84
+ results = []
85
+ queries.each_with_index do |query, idx|
86
+ multiplex.dataloader.enqueue {
87
+ results[idx] = begin_query(query, multiplex)
88
+ }
84
89
  end
85
90
 
91
+ multiplex.dataloader.run
92
+
86
93
  # Then, work through lazy results in a breadth-first way
87
- multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex)
94
+ multiplex.dataloader.enqueue {
95
+ multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex)
96
+ }
97
+ multiplex.dataloader.run
88
98
 
89
99
  # Then, find all errors and assign the result to the query object
90
- results.each_with_index.map do |data_result, idx|
100
+ results.each_with_index do |data_result, idx|
91
101
  query = queries[idx]
92
102
  finish_query(data_result, query, multiplex)
93
103
  # Get the Query::Result, not the Hash
94
- query.result
104
+ results[idx] = query.result
95
105
  end
106
+
107
+ results
96
108
  rescue Exception
97
109
  # TODO rescue at a higher level so it will catch errors in analysis, too
98
110
  # Assign values here so that the query's `@executed` becomes true
@@ -144,6 +156,8 @@ module GraphQL
144
156
 
145
157
  # use the old `query_execution_strategy` etc to run this query
146
158
  def run_one_legacy(schema, query)
159
+ warn "Multiplex.run_one_legacy will be removed from GraphQL-Ruby 2.0, upgrade to the Interpreter to avoid this deprecated codepath: https://graphql-ruby.org/queries/interpreter.html"
160
+
147
161
  query.result_values = if !query.valid?
148
162
  all_errors = query.validation_errors + query.analysis_errors + query.context.errors
149
163
  if all_errors.any?
@@ -2,6 +2,10 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class Function
5
+ def self.inherited(subclass)
6
+ warn "GraphQL::Function (used for #{subclass}) will be removed from GraphQL-Ruby 2.0, please upgrade to resolvers: https://graphql-ruby.org/fields/resolvers.html"
7
+ end
8
+
5
9
  # @return [Hash<String => GraphQL::Argument>] Arguments, keyed by name
6
10
  def arguments
7
11
  self.class.arguments
@@ -2,6 +2,8 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class InputObjectType < GraphQL::BaseType
5
+ extend Define::InstanceDefinable::DeprecatedDefine
6
+
5
7
  accepts_definitions(
6
8
  :arguments, :mutation,
7
9
  input_field: GraphQL::Define::AssignArgument,
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::Int` is given an input value outside of 32-bit integer range.
4
+ #
5
+ # For really big integer values, consider `GraphQL::Types::BigInt`
6
+ #
7
+ # @see GraphQL::Types::Int which raises this error
8
+ class IntegerDecodingError < GraphQL::RuntimeTypeError
9
+ # The value which couldn't be decoded
10
+ attr_reader :integer_value
11
+
12
+ def initialize(value)
13
+ @integer_value = value
14
+ super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.")
15
+ end
16
+ end
17
+ end
@@ -2,10 +2,12 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class InterfaceType < GraphQL::BaseType
5
+ extend Define::InstanceDefinable::DeprecatedDefine
6
+
5
7
  accepts_definitions :fields, :orphan_types, :resolve_type, field: GraphQL::Define::AssignObjectField
6
8
 
7
9
  attr_accessor :fields, :orphan_types, :resolve_type_proc
8
- attr_writer :type_membership_class
10
+ attr_writer :type_membership_class
9
11
  ensure_defined :fields, :orphan_types, :resolve_type_proc, :resolve_type
10
12
 
11
13
  def initialize
@@ -1,6 +1,102 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  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
4
100
  end
5
101
  end
6
102
 
@@ -7,7 +7,9 @@ module GraphQL
7
7
  "a name, potentially a list of arguments, and a return type."
8
8
  field :name, String, null: false
9
9
  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
11
13
  field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
12
14
  field :is_deprecated, Boolean, null: false
13
15
  field :deprecation_reason, String, null: true
@@ -16,8 +18,10 @@ module GraphQL
16
18
  !!@object.deprecation_reason
17
19
  end
18
20
 
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
21
25
  end
22
26
  end
23
27
  end
@@ -10,6 +10,12 @@ module GraphQL
10
10
  field :description, String, null: true
11
11
  field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
12
12
  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
13
19
 
14
20
  def default_value
15
21
  if @object.default_value?
@@ -1,93 +1,7 @@
1
1
  # 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
+
@@ -22,7 +22,9 @@ module GraphQL
22
22
  field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], null: true do
23
23
  argument :include_deprecated, Boolean, required: false, default_value: false
24
24
  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
26
28
  field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), null: true
27
29
 
28
30
  def name
@@ -55,9 +57,11 @@ module GraphQL
55
57
  end
56
58
  end
57
59
 
58
- def input_fields
60
+ def input_fields(include_deprecated:)
59
61
  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
61
65
  else
62
66
  nil
63
67
  end
@@ -39,7 +39,7 @@ module GraphQL
39
39
  end
40
40
 
41
41
  def inspect
42
- if name.nil? && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
42
+ if (name.nil? || parent_class.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
43
43
  "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError"
44
44
  else
45
45
  super
@@ -2,10 +2,21 @@
2
2
  module GraphQL
3
3
  module Language
4
4
  module BlockString
5
+ if !String.method_defined?(:match?)
6
+ using GraphQL::StringMatchBackport
7
+ end
8
+
5
9
  # Remove leading and trailing whitespace from a block string.
6
10
  # See "Block Strings" in https://github.com/facebook/graphql/blob/master/spec/Section%202%20--%20Language.md
7
11
  def self.trim_whitespace(str)
8
- lines = str.split("\n")
12
+ # Early return for the most common cases:
13
+ if str == ""
14
+ return ""
15
+ elsif !(has_newline = str.include?("\n")) && !(str.start_with?(" "))
16
+ return str
17
+ end
18
+
19
+ lines = has_newline ? str.split("\n") : [str]
9
20
  common_indent = nil
10
21
 
11
22
  # find the common whitespace
@@ -14,19 +25,27 @@ module GraphQL
14
25
  next
15
26
  end
16
27
  line_length = line.size
17
- line_indent = line[/\A */].size
28
+ line_indent = if line.match?(/\A [^ ]/)
29
+ 2
30
+ elsif line.match?(/\A [^ ]/)
31
+ 4
32
+ elsif line.match?(/\A[^ ]/)
33
+ 0
34
+ else
35
+ line[/\A */].size
36
+ end
18
37
  if line_indent < line_length && (common_indent.nil? || line_indent < common_indent)
19
38
  common_indent = line_indent
20
39
  end
21
40
  end
22
41
 
23
42
  # Remove the common whitespace
24
- if common_indent
43
+ if common_indent && common_indent > 0
25
44
  lines.each_with_index do |line, idx|
26
45
  if idx == 0
27
46
  next
28
47
  else
29
- line[0, common_indent] = ""
48
+ line.slice!(0, common_indent)
30
49
  end
31
50
  end
32
51
  end
@@ -40,7 +59,7 @@ module GraphQL
40
59
  end
41
60
 
42
61
  # Rebuild the string
43
- lines.join("\n")
62
+ lines.size > 1 ? lines.join("\n") : (lines.first || "")
44
63
  end
45
64
 
46
65
  def self.print(str, indent: '')