graphql 1.8.0.pre4 → 1.8.0.pre5

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/base_type.rb +10 -27
  3. data/lib/graphql/compatibility/query_parser_specification.rb +7 -0
  4. data/lib/graphql/field.rb +3 -3
  5. data/lib/graphql/internal_representation/node.rb +32 -13
  6. data/lib/graphql/internal_representation/visit.rb +3 -6
  7. data/lib/graphql/language.rb +1 -0
  8. data/lib/graphql/language/block_string.rb +47 -0
  9. data/lib/graphql/language/lexer.rb +129 -68
  10. data/lib/graphql/language/lexer.rl +13 -4
  11. data/lib/graphql/language/nodes.rb +6 -3
  12. data/lib/graphql/language/printer.rb +1 -1
  13. data/lib/graphql/language/token.rb +1 -1
  14. data/lib/graphql/query.rb +1 -1
  15. data/lib/graphql/relay.rb +1 -0
  16. data/lib/graphql/relay/connection_type.rb +5 -3
  17. data/lib/graphql/relay/edge_type.rb +2 -1
  18. data/lib/graphql/relay/type_extensions.rb +30 -0
  19. data/lib/graphql/schema.rb +25 -0
  20. data/lib/graphql/schema/build_from_definition.rb +2 -0
  21. data/lib/graphql/schema/field.rb +3 -0
  22. data/lib/graphql/schema/finder.rb +153 -0
  23. data/lib/graphql/schema/member.rb +3 -1
  24. data/lib/graphql/schema/printer.rb +1 -1
  25. data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
  26. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +11 -1
  27. data/lib/graphql/tracing/data_dog_tracing.rb +13 -9
  28. data/lib/graphql/upgrader/member.rb +19 -8
  29. data/lib/graphql/version.rb +1 -1
  30. data/spec/graphql/backtrace_spec.rb +10 -0
  31. data/spec/graphql/directive_spec.rb +3 -1
  32. data/spec/graphql/language/block_string_spec.rb +70 -0
  33. data/spec/graphql/language/lexer_spec.rb +9 -0
  34. data/spec/graphql/query_spec.rb +1 -1
  35. data/spec/graphql/schema/field_spec.rb +8 -0
  36. data/spec/graphql/schema/finder_spec.rb +135 -0
  37. data/spec/graphql/schema/printer_spec.rb +48 -5
  38. data/spec/graphql/schema_spec.rb +7 -0
  39. data/spec/graphql/upgrader/member_spec.rb +25 -0
  40. metadata +23 -2
@@ -102,7 +102,17 @@ module GraphQL
102
102
  def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
103
103
  spreads = spreads_for_context[node] - visited_fragments
104
104
  spreads.each do |spread_name|
105
- def_node, variables = fragment_definitions.find { |def_node, vars| def_node.name == spread_name }
105
+ def_node = nil
106
+ variables = nil
107
+ # Implement `.find` by hand to avoid Ruby's internal allocations
108
+ fragment_definitions.each do |frag_def_node, vars|
109
+ if frag_def_node.name == spread_name
110
+ def_node = frag_def_node
111
+ variables = vars
112
+ break
113
+ end
114
+ end
115
+
106
116
  next if !def_node
107
117
  visited_fragments << spread_name
108
118
  variables.each do |name, child_usage|
@@ -15,17 +15,12 @@ module GraphQL
15
15
  }
16
16
 
17
17
  def platform_trace(platform_key, key, data)
18
- service = options.fetch(:service, 'ruby-graphql')
18
+ tracer.trace(platform_key, service: service_name) do |span|
19
+ span.span_type = 'custom'
19
20
 
20
- pin = Datadog::Pin.get_from(self)
21
- unless pin
22
- pin = Datadog::Pin.new(service)
23
- pin.onto(self)
24
- end
25
-
26
- pin.tracer.trace(platform_key, service: pin.service) do |span|
27
21
  if key == 'execute_multiplex'
28
- span.resource = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
22
+ operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
23
+ span.resource = operations unless operations.empty?
29
24
  end
30
25
 
31
26
  if key == 'execute_query'
@@ -33,10 +28,19 @@ module GraphQL
33
28
  span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type)
34
29
  span.set_tag(:query_string, data[:query].query_string)
35
30
  end
31
+
36
32
  yield
37
33
  end
38
34
  end
39
35
 
36
+ def service_name
37
+ options.fetch(:service, 'ruby-graphql')
38
+ end
39
+
40
+ def tracer
41
+ options.fetch(:tracer, Datadog.tracer)
42
+ end
43
+
40
44
  def platform_field_key(type, field)
41
45
  "#{type.name}.#{field.name}"
42
46
  end
@@ -52,6 +52,13 @@ module GraphQL
52
52
  type_expr
53
53
  end
54
54
  end
55
+
56
+ def underscorize(str)
57
+ str
58
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
59
+ .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
60
+ .downcase
61
+ end
55
62
  end
56
63
 
57
64
  # Turns `{X} = GraphQL::{Y}Type.define do` into `class {X} < Types::Base{Y}`.
@@ -187,20 +194,13 @@ module GraphQL
187
194
  # (They'll be automatically camelized later.)
188
195
  class UnderscoreizeFieldNameTransform < Transform
189
196
  def apply(input_text)
190
- input_text.sub /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)/ do
197
+ input_text.gsub /(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)/ do
191
198
  field_type = $~[:field_type]
192
199
  camelized_name = $~[:name]
193
200
  underscored_name = underscorize(camelized_name)
194
201
  "#{field_type} :#{underscored_name}"
195
202
  end
196
203
  end
197
-
198
- def underscorize(str)
199
- str
200
- .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
201
- .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
202
- .downcase
203
- end
204
204
  end
205
205
 
206
206
  class ResolveProcToMethodTransform < Transform
@@ -215,6 +215,8 @@ module GraphQL
215
215
  # - Args is trickier:
216
216
  # - If it's not used, remove it
217
217
  # - If it's used, abandon ship and make it `**args`
218
+ # - Convert string args access to symbol access, since it's a Ruby **splat
219
+ # - Convert camelized arg names to underscored arg names
218
220
  # - (It would be nice to correctly become Ruby kwargs, but that might be too hard)
219
221
  # - Add a `# TODO` comment to the method source?
220
222
  # - Rebuild the method:
@@ -260,6 +262,15 @@ module GraphQL
260
262
  lines.unshift("\n#{method_def_indent}#{method_def}")
261
263
  lines << "#{method_def_indent}end\n"
262
264
  method_body = lines.join("\n")
265
+ # Update Argument access to be underscore and symbols
266
+ # Update `args[...]` and `args.key?`
267
+ method_body = method_body.gsub(/#{args_arg_name}(?<method_begin>\.key\?\(?|\[)["':](?<arg_name>[a-zA-Z0-9_]+)["']?(?<method_end>\]|\))?/) do
268
+ method_begin = $~[:method_begin]
269
+ arg_name = underscorize($~[:arg_name])
270
+ method_end = $~[:method_end]
271
+ "#{args_arg_name}#{method_begin}:#{arg_name}#{method_end}"
272
+ end
273
+
263
274
  # Replace the resolve proc with the method
264
275
  input_text[processor.resolve_start..processor.resolve_end] = ""
265
276
  # The replacement above might have left some preceeding whitespace,
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.0.pre4"
3
+ VERSION = "1.8.0.pre5"
4
4
  end
@@ -126,6 +126,16 @@ describe GraphQL::Backtrace do
126
126
  assert_includes err.message, "more lines"
127
127
  end
128
128
 
129
+ it "annotates errors from Query#result" do
130
+ query_str = "query StrField { field2 { strField } __typename }"
131
+ context = { backtrace: true }
132
+ query = GraphQL::Query.new(schema, query_str, context: context)
133
+ err = assert_raises(GraphQL::Backtrace::TracedError) {
134
+ query.result
135
+ }
136
+ assert_instance_of RuntimeError, err.cause
137
+ end
138
+
129
139
  it "annotates errors inside lazy resolution" do
130
140
  # Test context-based flag
131
141
  err = assert_raises(GraphQL::Backtrace::TracedError) {
@@ -31,7 +31,9 @@ describe GraphQL::Directive do
31
31
  describe "child fields" do
32
32
  let(:query_string) { <<-GRAPHQL
33
33
  {
34
- __type(name: "Cheese") {
34
+ __type(name: """
35
+ Cheese
36
+ """) {
35
37
  fields { name }
36
38
  fields @skip(if: true) { isDeprecated }
37
39
  }
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Language::BlockString do
5
+ describe "trimming whitespace" do
6
+ def trim_whitespace(str)
7
+ GraphQL::Language::BlockString.trim_whitespace(str)
8
+ end
9
+
10
+ it "matches the examples in graphql-js" do
11
+ # these are taken from:
12
+ # https://github.com/graphql/graphql-js/blob/36ec0e9d34666362ff0e2b2b18edeb98e3c9abee/src/language/__tests__/blockStringValue-test.js#L12
13
+ # A set of [before, after] pairs:
14
+ examples = [
15
+ [
16
+ # Removes common whitespace:
17
+ "
18
+ Hello,
19
+ World!
20
+
21
+ Yours,
22
+ GraphQL.
23
+ ",
24
+ "Hello,\n World!\n\nYours,\n GraphQL."
25
+ ],
26
+ [
27
+ # Removes leading and trailing newlines:
28
+ "
29
+
30
+ Hello,
31
+ World!
32
+
33
+ Yours,
34
+ GraphQL.
35
+
36
+ ",
37
+ "Hello,\n World!\n\nYours,\n GraphQL."
38
+ ],
39
+ [
40
+ # Removes blank lines (with whitespace _and_ newlines:)
41
+ "\n \n
42
+ Hello,
43
+ World!
44
+
45
+ Yours,
46
+ GraphQL.
47
+
48
+ \n \n",
49
+ "Hello,\n World!\n\nYours,\n GraphQL."
50
+ ],
51
+ [
52
+ # Retains indentation from the first line
53
+ " Hello,\n World!\n\n Yours,\n GraphQL.",
54
+ " Hello,\n World!\n\nYours,\n GraphQL.",
55
+ ],
56
+ [
57
+ # Doesn't alter trailing spaces
58
+ "\n \n Hello, \n World! \n\n Yours, \n GraphQL. ",
59
+ "Hello, \n World! \n\nYours, \n GraphQL. ",
60
+
61
+ ],
62
+ ]
63
+
64
+ examples.each_with_index do |(before, after), idx|
65
+ transformed_str = trim_whitespace(before)
66
+ assert_equal(after, transformed_str, "Example ##{idx + 1}")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -26,6 +26,15 @@ describe GraphQL::Language::Lexer do
26
26
  assert_equal tokens[0], tokens[1].prev_token
27
27
  end
28
28
 
29
+ describe "block strings" do
30
+ let(:query_string) { %|{ a(b: """\nc\n d\n""")}|}
31
+
32
+ it "tokenizes them" do
33
+ str_token = tokens[5]
34
+ assert_equal "c\n d", str_token.value
35
+ end
36
+ end
37
+
29
38
  it "unescapes escaped characters" do
30
39
  assert_equal "\" \\ / \b \f \n \r \t", subject.tokenize('"\\" \\\\ \\/ \\b \\f \\n \\r \\t"').first.to_s
31
40
  end
@@ -85,7 +85,7 @@ describe GraphQL::Query do
85
85
  operation_name: operation_name,
86
86
  max_depth: max_depth,
87
87
  )
88
- query.query_string = '{ __type(name: "Cheese") { name } }'
88
+ query.query_string = '{ __type(name: """Cheese""") { name } }'
89
89
  assert_equal "Cheese", query.result["data"] ["__type"]["name"]
90
90
  end
91
91
  end
@@ -15,6 +15,14 @@ describe GraphQL::Schema::Field do
15
15
  assert_equal 'inspectInput', field.graphql_definition.name
16
16
  end
17
17
 
18
+ it "exposes the method override" do
19
+ assert_nil field.method
20
+ object = Class.new(Jazz::BaseObject) do
21
+ field :t, String, method: :tt, null: true
22
+ end
23
+ assert_equal :tt, object.fields["t"].method
24
+ end
25
+
18
26
  it "accepts a block for definition" do
19
27
  object = Class.new(Jazz::BaseObject) do
20
28
  graphql_name "JustAName"
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Schema::Finder do
5
+ let(:finder) { GraphQL::Schema::Finder.new(Jazz::Schema) }
6
+
7
+ describe "#find" do
8
+ it "finds a valid object type" do
9
+ type = finder.find("Ensemble")
10
+ assert_equal "Ensemble", type.name
11
+ end
12
+
13
+ it "raises when finding an invalid object type" do
14
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
15
+ finder.find("DoesNotExist")
16
+ end
17
+
18
+ assert_match /Could not find type `DoesNotExist` in schema./, exception.message
19
+ end
20
+
21
+ it "finds a valid directive" do
22
+ directive = finder.find("@include")
23
+ assert_equal "include", directive.name
24
+ end
25
+
26
+ it "raises when finding an invalid directive" do
27
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
28
+ finder.find("@yolo")
29
+ end
30
+
31
+ assert_match /Could not find directive `@yolo` in schema./, exception.message
32
+ end
33
+
34
+ it "finds a valid field" do
35
+ field = finder.find("Ensemble.musicians")
36
+ assert_equal "musicians", field.name
37
+ end
38
+
39
+ it "finds a meta field" do
40
+ field = finder.find("Ensemble.__typename")
41
+ assert_equal "__typename", field.name
42
+ end
43
+
44
+ it "raises when finding an in valid field" do
45
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
46
+ finder.find("Ensemble.nope")
47
+ end
48
+
49
+ assert_match /Could not find field `nope` on object type `Ensemble`./, exception.message
50
+ end
51
+
52
+ it "finds a valid argument" do
53
+ arg = finder.find("Query.find.id")
54
+ assert_equal "id", arg.name
55
+ end
56
+
57
+ it "raises when finding an invalid argument" do
58
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
59
+ finder.find("Query.find.thisArgumentIsInvalid")
60
+ end
61
+
62
+ assert_match /Could not find argument `thisArgumentIsInvalid` on field `find`./, exception.message
63
+ end
64
+
65
+ it "raises when selecting on an argument" do
66
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
67
+ finder.find("Query.find.id.whyYouDoThis")
68
+ end
69
+
70
+ assert_match /Cannot select member `whyYouDoThis` on a field./, exception.message
71
+ end
72
+
73
+ it "finds a valid interface" do
74
+ type = finder.find("NamedEntity")
75
+ assert_equal "NamedEntity", type.name
76
+ end
77
+
78
+ it "finds a valid input type" do
79
+ type = finder.find("LegacyInput")
80
+ assert_equal "LegacyInput", type.name
81
+ end
82
+
83
+ it "finds a valid input field" do
84
+ input_field = finder.find("LegacyInput.intValue")
85
+ assert_equal "intValue", input_field.name
86
+ end
87
+
88
+ it "raises when finding an invalid input field" do
89
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
90
+ finder.find("LegacyInput.wat")
91
+ end
92
+
93
+ assert_match /Could not find input field `wat` on input object type `LegacyInput`./, exception.message
94
+ end
95
+
96
+ it "finds a valid union type" do
97
+ type = finder.find("PerformingAct")
98
+ assert_equal "PerformingAct", type.name
99
+ end
100
+
101
+ it "raises when selecting a possible type" do
102
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
103
+ finder.find("PerformingAct.Musician")
104
+ end
105
+
106
+ assert_match /Cannot select union possible type `Musician`. Select the type directly instead./, exception.message
107
+ end
108
+
109
+ it "finds a valid enum type" do
110
+ type = finder.find("Family")
111
+ assert_equal "Family", type.name
112
+ end
113
+
114
+ it "finds a valid enum value" do
115
+ value = finder.find("Family.BRASS")
116
+ assert_equal "BRASS", value.name
117
+ end
118
+
119
+ it "raises when finding an invalid enum value" do
120
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
121
+ finder.find("Family.THISISNOTASTATUS")
122
+ end
123
+
124
+ assert_match /Could not find enum value `THISISNOTASTATUS` on enum type `Family`./, exception.message
125
+ end
126
+
127
+ it "raises when selecting on an enum value" do
128
+ exception = assert_raises GraphQL::Schema::Finder::MemberNotFoundError do
129
+ finder.find("Family.BRASS.wat")
130
+ end
131
+
132
+ assert_match /Cannot select member `wat` on an enum value./, exception.message
133
+ end
134
+ end
135
+ end
@@ -14,7 +14,11 @@ describe GraphQL::Schema::Printer do
14
14
 
15
15
  value "FOO", value: :foo
16
16
  value "BAR", value: :bar
17
- value "BAZ", deprecation_reason: 'Use "BAR".'
17
+ value "BAZ", deprecation_reason: <<-REASON
18
+ Use "BAR" instead.
19
+
20
+ It's the replacement for this value.
21
+ REASON
18
22
  value "WOZ", deprecation_reason: GraphQL::Directive::DEFAULT_DEPRECATION_REASON
19
23
  end
20
24
 
@@ -352,7 +356,6 @@ SCHEMA
352
356
  custom_mutation = schema.mutation.redefine(name: "MyMutationRoot")
353
357
  custom_schema = schema.redefine(mutation: custom_mutation)
354
358
 
355
-
356
359
  expected = <<SCHEMA
357
360
  schema {
358
361
  query: Query
@@ -389,7 +392,7 @@ type Audio {
389
392
 
390
393
  enum Choice {
391
394
  BAR
392
- BAZ @deprecated(reason: "Use "BAR".")
395
+ BAZ @deprecated(reason: "Use \\\"BAR\\\" instead.\\n\\nIt's the replacement for this value.\\n")
393
396
  FOO
394
397
  WOZ @deprecated
395
398
  }
@@ -438,7 +441,7 @@ interface Node {
438
441
  type Post {
439
442
  body: String!
440
443
  comments: [Comment!]
441
- comments_count: Int! @deprecated(reason: "Use "comments".")
444
+ comments_count: Int! @deprecated(reason: "Use \\\"comments\\\".")
442
445
  id: ID!
443
446
  title: String!
444
447
  }
@@ -600,7 +603,7 @@ SCHEMA
600
603
  type Post {
601
604
  body: String!
602
605
  comments: [Comment!]
603
- comments_count: Int! @deprecated(reason: "Use "comments".")
606
+ comments_count: Int! @deprecated(reason: "Use \\\"comments\\\".")
604
607
  id: ID!
605
608
  title: String!
606
609
  }
@@ -608,4 +611,44 @@ SCHEMA
608
611
  assert_equal expected.chomp, GraphQL::Schema::Printer.new(schema).print_type(schema.types['Post'])
609
612
  end
610
613
  end
614
+
615
+ describe "#print_directive" do
616
+ it "prints the deprecation reason in a single line escaped string including line breaks" do
617
+ expected = <<SCHEMA
618
+ enum Choice {
619
+ BAR
620
+ BAZ @deprecated(reason: "Use \\\"BAR\\\" instead.\\n\\nIt's the replacement for this value.\\n")
621
+ FOO
622
+ WOZ @deprecated
623
+ }
624
+
625
+ type Subscription {
626
+ }
627
+
628
+ input Varied {
629
+ bool: Boolean
630
+ enum: Choice = FOO
631
+ float: Float
632
+ int: Int
633
+ }
634
+ SCHEMA
635
+
636
+ only_filter = ->(member, ctx) {
637
+ case member
638
+ when GraphQL::ScalarType
639
+ true
640
+ when GraphQL::BaseType
641
+ ctx[:names].include?(member.name)
642
+ when GraphQL::Argument
643
+ member.name != "id"
644
+ else
645
+ true
646
+ end
647
+ }
648
+
649
+ context = { names: ["Varied", "Choice", "Subscription"] }
650
+
651
+ assert_equal expected.chomp, GraphQL::Schema::Printer.new(schema, context: context, only: only_filter).print_schema
652
+ end
653
+ end
611
654
  end