graphql 1.8.0.pre4 → 1.8.0.pre5

Sign up to get free protection for your applications and to get access to all the features.
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