graphql 1.8.0.pre7 → 1.8.0.pre8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/argument.rb +24 -19
  3. data/lib/graphql/backtrace/tracer.rb +16 -22
  4. data/lib/graphql/base_type.rb +6 -1
  5. data/lib/graphql/execution/execute.rb +15 -13
  6. data/lib/graphql/execution/lazy/resolve.rb +1 -3
  7. data/lib/graphql/interface_type.rb +5 -3
  8. data/lib/graphql/language/document_from_schema_definition.rb +1 -1
  9. data/lib/graphql/language/lexer.rb +65 -51
  10. data/lib/graphql/language/lexer.rl +2 -0
  11. data/lib/graphql/language/nodes.rb +118 -71
  12. data/lib/graphql/language/parser.rb +699 -652
  13. data/lib/graphql/language/parser.y +11 -5
  14. data/lib/graphql/language/printer.rb +2 -2
  15. data/lib/graphql/object_type.rb +0 -5
  16. data/lib/graphql/relay/relation_connection.rb +1 -1
  17. data/lib/graphql/schema.rb +15 -3
  18. data/lib/graphql/schema/argument.rb +18 -1
  19. data/lib/graphql/schema/enum.rb +5 -2
  20. data/lib/graphql/schema/enum_value.rb +8 -1
  21. data/lib/graphql/schema/field.rb +10 -3
  22. data/lib/graphql/schema/input_object.rb +4 -2
  23. data/lib/graphql/schema/interface.rb +15 -0
  24. data/lib/graphql/schema/member.rb +2 -1
  25. data/lib/graphql/schema/member/accepts_definition.rb +118 -0
  26. data/lib/graphql/schema/member/build_type.rb +2 -2
  27. data/lib/graphql/schema/member/has_fields.rb +3 -2
  28. data/lib/graphql/schema/object.rb +4 -2
  29. data/lib/graphql/schema/scalar.rb +2 -0
  30. data/lib/graphql/schema/traversal.rb +3 -0
  31. data/lib/graphql/schema/union.rb +6 -11
  32. data/lib/graphql/version.rb +1 -1
  33. data/spec/graphql/argument_spec.rb +21 -0
  34. data/spec/graphql/base_type_spec.rb +22 -0
  35. data/spec/graphql/enum_type_spec.rb +18 -5
  36. data/spec/graphql/execution/execute_spec.rb +3 -3
  37. data/spec/graphql/input_object_type_spec.rb +13 -0
  38. data/spec/graphql/interface_type_spec.rb +12 -0
  39. data/spec/graphql/language/nodes_spec.rb +0 -12
  40. data/spec/graphql/language/parser_spec.rb +74 -0
  41. data/spec/graphql/language/printer_spec.rb +1 -1
  42. data/spec/graphql/object_type_spec.rb +21 -0
  43. data/spec/graphql/relay/range_add_spec.rb +5 -1
  44. data/spec/graphql/relay/relation_connection_spec.rb +7 -1
  45. data/spec/graphql/schema/argument_spec.rb +31 -0
  46. data/spec/graphql/schema/enum_spec.rb +5 -0
  47. data/spec/graphql/schema/field_spec.rb +11 -1
  48. data/spec/graphql/schema/input_object_spec.rb +5 -0
  49. data/spec/graphql/schema/interface_spec.rb +29 -0
  50. data/spec/graphql/schema/member/accepts_definition_spec.rb +62 -0
  51. data/spec/graphql/schema/printer_spec.rb +34 -0
  52. data/spec/graphql/schema/traversal_spec.rb +31 -0
  53. data/spec/graphql/schema/union_spec.rb +30 -0
  54. data/spec/graphql/schema_spec.rb +6 -0
  55. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +2 -2
  56. data/spec/graphql/tracing/active_support_notifications_tracing_spec.rb +1 -1
  57. data/spec/graphql/union_type_spec.rb +1 -1
  58. data/spec/spec_helper.rb +1 -0
  59. data/spec/support/dummy/schema.rb +1 -4
  60. metadata +7 -2
@@ -109,8 +109,8 @@ module GraphQL
109
109
  return string unless string.include?("_")
110
110
  camelized = string.split('_').map(&:capitalize).join
111
111
  camelized[0] = camelized[0].downcase
112
- if string.start_with?("__")
113
- camelized = "__#{camelized}"
112
+ if (match_data = string.match(/\A(_+)/))
113
+ camelized = "#{match_data[0]}#{camelized}"
114
114
  end
115
115
  camelized
116
116
  end
@@ -7,8 +7,9 @@ module GraphQL
7
7
  # Add a field to this object or interface with the given definition
8
8
  # @see {GraphQL::Schema::Field#initialize} for method signature
9
9
  # @return [void]
10
- def field(*args, &block)
11
- field_defn = field_class.new(*args, &block)
10
+ def field(*args, **kwargs, &block)
11
+ kwargs[:owner] = self
12
+ field_defn = field_class.new(*args, **kwargs, &block)
12
13
  add_field(field_defn)
13
14
  nil
14
15
  end
@@ -3,6 +3,8 @@
3
3
  module GraphQL
4
4
  class Schema
5
5
  class Object < GraphQL::Schema::Member
6
+ extend GraphQL::Schema::Member::AcceptsDefinition
7
+
6
8
  # @return [Object] the application object this type is wrapping
7
9
  attr_reader :object
8
10
 
@@ -22,8 +24,8 @@ module GraphQL
22
24
  new_interfaces.each do |int|
23
25
  if int.is_a?(Class) && int < GraphQL::Schema::Interface
24
26
  # Add the graphql field defns
25
- int.fields.each do |_name, field|
26
- add_field(field)
27
+ int.fields.each do |name, field|
28
+ own_fields[name] = field
27
29
  end
28
30
  # And call the implemented hook
29
31
  int.apply_implemented(self)
@@ -2,6 +2,8 @@
2
2
  module GraphQL
3
3
  class Schema
4
4
  class Scalar < GraphQL::Schema::Member
5
+ extend GraphQL::Schema::Member::AcceptsDefinition
6
+
5
7
  class << self
6
8
  def coerce_input(val, ctx)
7
9
  raise NotImplementedError, "#{self.name}.coerce_input(val, ctx) must prepare GraphQL input (#{val.inspect}) for Ruby processing"
@@ -136,6 +136,9 @@ Some late-bound types couldn't be resolved:
136
136
  visit_fields(schema, type_defn)
137
137
  when GraphQL::InterfaceType
138
138
  visit_fields(schema, type_defn)
139
+ type_defn.orphan_types.each do |t|
140
+ visit(schema, t, "Orphan type for #{type_defn.name}")
141
+ end
139
142
  when GraphQL::UnionType
140
143
  type_defn.possible_types.each do |t|
141
144
  @union_memberships[t.name] << type_defn
@@ -2,30 +2,25 @@
2
2
  module GraphQL
3
3
  class Schema
4
4
  class Union < GraphQL::Schema::Member
5
+ extend GraphQL::Schema::Member::AcceptsDefinition
6
+
5
7
  class << self
6
8
  def possible_types(*types)
7
9
  if types.any?
8
- @own_possible_types = types
10
+ @possible_types = types
9
11
  else
10
- all_possible_types = own_possible_types
11
- inherited_possible_types = (superclass < GraphQL::Schema::Union ? superclass.possible_types : [])
12
- all_possible_types += inherited_possible_types
12
+ all_possible_types = @possible_types || []
13
+ all_possible_types += super if defined?(super)
13
14
  all_possible_types.uniq
14
15
  end
15
16
  end
16
17
 
17
- def own_possible_types
18
- @own_possible_types ||= []
19
- end
20
-
21
18
  def to_graphql
22
19
  type_defn = GraphQL::UnionType.new
23
20
  type_defn.name = graphql_name
24
21
  type_defn.description = description
25
22
  type_defn.possible_types = possible_types
26
- # If an instance method is defined, use it as a
27
- # resolve type hook, via the class method
28
- if method_defined?(:resolve_type)
23
+ if respond_to?(:resolve_type)
29
24
  type_defn.resolve_type = method(:resolve_type)
30
25
  end
31
26
  type_defn
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.0.pre7"
3
+ VERSION = "1.8.0.pre8"
4
4
  end
@@ -32,6 +32,27 @@ describe GraphQL::Argument do
32
32
  assert_equal GraphQL::STRING_TYPE, arg.type
33
33
  end
34
34
 
35
+ it "accepts a definition block after defining kwargs" do
36
+ arg = GraphQL::Argument.from_dsl(:foo, GraphQL::STRING_TYPE) do
37
+ description "my type is #{target.type}"
38
+ end
39
+
40
+ assert_equal "my type is String", arg.description
41
+ end
42
+
43
+ it "accepts a definition block with existing arg" do
44
+ existing = GraphQL::Argument.define do
45
+ name "bar"
46
+ type GraphQL::STRING_TYPE
47
+ end
48
+
49
+ arg = GraphQL::Argument.from_dsl(:foo, existing) do
50
+ description "Description for an existing field."
51
+ end
52
+
53
+ assert_equal "Description for an existing field.", arg.description
54
+ end
55
+
35
56
  it "creates an argument from dsl arguments" do
36
57
  arg = GraphQL::Argument.from_dsl(
37
58
  :foo,
@@ -28,6 +28,28 @@ describe GraphQL::BaseType do
28
28
  assert_equal ["Cheese"], Dummy::CheeseType.metadata[:class_names]
29
29
  end
30
30
 
31
+ describe "#name" do
32
+ describe "when containing spaces" do
33
+ BaseNameSpaceTest = GraphQL::BaseType.define do
34
+ name "Some Invalid Name"
35
+ end
36
+
37
+ it "is invalid" do
38
+ assert_raises(GraphQL::InvalidNameError) { BaseNameSpaceTest.name }
39
+ end
40
+ end
41
+
42
+ describe "when containing colons" do
43
+ BaseNameColonsTest = GraphQL::BaseType.define do
44
+ name "Some::Invalid::Name"
45
+ end
46
+
47
+ it 'is invalid' do
48
+ assert_raises(GraphQL::InvalidNameError) { BaseNameColonsTest.name }
49
+ end
50
+ end
51
+ end
52
+
31
53
  describe "name" do
32
54
  it "fails with a helpful message" do
33
55
  error = assert_raises RuntimeError do
@@ -37,21 +37,34 @@ describe GraphQL::EnumType do
37
37
  end
38
38
  end
39
39
 
40
- describe "invalid names" do
41
- it "rejects names with a space" do
40
+ describe "invalid values" do
41
+ it "rejects value names with a space" do
42
42
  assert_raises(GraphQL::InvalidNameError) {
43
- InvalidEnumTest = GraphQL::EnumType.define do
44
- name "InvalidEnumTest"
43
+ InvalidEnumValueTest = GraphQL::EnumType.define do
44
+ name "InvalidEnumValueTest"
45
45
 
46
46
  value("SPACE IN VALUE", "Invalid enum because it contains spaces", value: 1)
47
47
  end
48
48
 
49
49
  # Force evaluation
50
- InvalidEnumTest.name
50
+ InvalidEnumValueTest.name
51
51
  }
52
52
  end
53
53
  end
54
54
 
55
+ describe "invalid name" do
56
+ it "reject names with invalid format" do
57
+ assert_raises(GraphQL::InvalidNameError) do
58
+ InvalidEnumNameTest = GraphQL::EnumType.define do
59
+ name "Some::Invalid::Name"
60
+ end
61
+
62
+ # Force evaluation
63
+ InvalidEnumNameTest.name
64
+ end
65
+ end
66
+ end
67
+
55
68
  describe "values that are Arrays" do
56
69
  let(:schema) {
57
70
  enum = GraphQL::EnumType.define do
@@ -163,10 +163,10 @@ describe GraphQL::Execution::Execute do
163
163
  "execute_field",
164
164
  "execute_query",
165
165
  "lazy_loader",
166
- "execute_field",
167
166
  "execute_field_lazy",
168
167
  "execute_field",
169
168
  "execute_field_lazy",
169
+ "execute_field",
170
170
  "execute_field_lazy",
171
171
  "execute_field_lazy",
172
172
  "execute_query_lazy",
@@ -177,8 +177,8 @@ describe GraphQL::Execution::Execute do
177
177
  field_1_eager, field_2_eager,
178
178
  query_eager, lazy_loader,
179
179
  # field 3 is eager-resolved _during_ field 1's lazy resolve
180
- field_3_eager, field_1_lazy,
181
- field_4_eager, field_2_lazy,
180
+ field_1_lazy, field_3_eager,
181
+ field_2_lazy, field_4_eager,
182
182
  # field 3 didn't finish above, it's resolved in the next round
183
183
  field_3_lazy, field_4_lazy,
184
184
  query_lazy, multiplex = exec_traces
@@ -218,6 +218,19 @@ describe GraphQL::InputObjectType do
218
218
  assert_equal(expected, actual)
219
219
  end
220
220
  end
221
+
222
+ describe 'with invalid name' do
223
+ it 'raises the correct error' do
224
+ assert_raises(GraphQL::InvalidNameError) do
225
+ InvalidInputTest = GraphQL::InputObjectType.define do
226
+ name "Some::Invalid Name"
227
+ end
228
+
229
+ # Force evaluation
230
+ InvalidInputTest.name
231
+ end
232
+ end
233
+ end
221
234
  end
222
235
  end
223
236
 
@@ -92,6 +92,18 @@ describe GraphQL::InterfaceType do
92
92
  assert_equal 3, interface.fields.size
93
93
  assert_equal 4, interface_2.fields.size
94
94
  end
95
+
96
+ it "copies orphan types without affecting the original" do
97
+ interface = GraphQL::InterfaceType.define do
98
+ name "AInterface"
99
+ orphan_types [Dummy::HoneyType]
100
+ end
101
+
102
+ interface_2 = interface.dup
103
+ interface_2.orphan_types << Dummy::CheeseType
104
+ assert_equal 1, interface.orphan_types.size
105
+ assert_equal 2, interface_2.orphan_types.size
106
+ end
95
107
  end
96
108
 
97
109
  describe "#resolve_type" do
@@ -2,18 +2,6 @@
2
2
  require "spec_helper"
3
3
 
4
4
  describe GraphQL::Language::Nodes::AbstractNode do
5
- describe "child and scalar attributes" do
6
- it "are inherited by node subclasses" do
7
- subclassed_directive = Class.new(GraphQL::Language::Nodes::Directive)
8
-
9
- assert_equal GraphQL::Language::Nodes::Directive.scalar_attributes,
10
- subclassed_directive.scalar_attributes
11
-
12
- assert_equal GraphQL::Language::Nodes::Directive.child_attributes,
13
- subclassed_directive.child_attributes
14
- end
15
- end
16
-
17
5
  describe "#filename" do
18
6
  it "is set after .parse_file" do
19
7
  filename = "spec/support/parser/filename_example.graphql"
@@ -44,6 +44,80 @@ describe GraphQL::Language::Parser do
44
44
  assert_equal schema_string, document.to_query_string
45
45
  end
46
46
 
47
+ describe "implements" do
48
+ it "parses when there are no interfaces" do
49
+ schema = "
50
+ type A {
51
+ a: String
52
+ }
53
+ "
54
+
55
+ document = subject.parse(schema)
56
+
57
+ assert_equal [], document.definitions[0].interfaces.map(&:name)
58
+ end
59
+
60
+ it "parses with leading ampersand" do
61
+ schema = "
62
+ type A implements & B {
63
+ a: String
64
+ }
65
+ "
66
+
67
+ document = subject.parse(schema)
68
+
69
+ assert_equal ["B"], document.definitions[0].interfaces.map(&:name)
70
+ end
71
+
72
+ it "parses with leading ampersand and multiple interfaces" do
73
+ schema = "
74
+ type A implements & B & C {
75
+ a: String
76
+ }
77
+ "
78
+
79
+ document = subject.parse(schema)
80
+
81
+ assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name)
82
+ end
83
+
84
+ it "parses without leading ampersand" do
85
+ schema = "
86
+ type A implements B {
87
+ a: String
88
+ }
89
+ "
90
+
91
+ document = subject.parse(schema)
92
+
93
+ assert_equal ["B"], document.definitions[0].interfaces.map(&:name)
94
+ end
95
+
96
+ it "parses without leading ampersand and multiple interfaces" do
97
+ schema = "
98
+ type A implements B & C {
99
+ a: String
100
+ }
101
+ "
102
+
103
+ document = subject.parse(schema)
104
+
105
+ assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name)
106
+ end
107
+
108
+ it "supports the old way of parsing multiple interfaces for backwards compatibility" do
109
+ schema = "
110
+ type A implements B, C {
111
+ a: String
112
+ }
113
+ "
114
+
115
+ document = subject.parse(schema)
116
+
117
+ assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name)
118
+ end
119
+ end
120
+
47
121
  describe ".parse_file" do
48
122
  it "assigns filename to all nodes" do
49
123
  example_filename = "spec/support/parser/filename_example.graphql"
@@ -115,7 +115,7 @@ describe GraphQL::Language::Printer do
115
115
  # Union description
116
116
  union AnnotatedUnion @onUnion = A | B
117
117
 
118
- type Foo implements Bar {
118
+ type Foo implements Bar & AnnotatedInterface {
119
119
  one: Type
120
120
  two(argument: InputType!): Type
121
121
  three(argument: InputType, other: String): Int
@@ -60,6 +60,27 @@ describe GraphQL::ObjectType do
60
60
 
61
61
  assert_raises(ArgumentError) { type.name }
62
62
  end
63
+
64
+ it "doesnt convolute field names that differ with underscore" do
65
+ interface = Class.new(GraphQL::Schema::Interface) do
66
+ graphql_name 'TestInterface'
67
+ description 'Requires an id'
68
+
69
+ field :id, GraphQL::ID_TYPE, null: false
70
+ end
71
+
72
+ object = Class.new(GraphQL::Schema::Object) do
73
+ graphql_name 'TestObject'
74
+ implements interface
75
+ global_id_field :id
76
+
77
+ # When the validation for `id` is run for `_id`, it will fail because
78
+ # GraphQL::STRING_TYPE cannot be transformed into a GraphQL::ID_TYPE
79
+ field :_id, String, description: 'database id', null: true
80
+ end
81
+
82
+ assert_equal nil, GraphQL::Schema::Validation.validate(object.to_graphql)
83
+ end
63
84
  end
64
85
 
65
86
  it "accepts fields definition" do
@@ -70,7 +70,11 @@ describe GraphQL::Relay::RangeAdd do
70
70
  field :add_item, add_item.field
71
71
  end
72
72
 
73
- GraphQL::Schema.define(query: query, mutation: mutation, cursor_encoder: PassThroughEncoder)
73
+ Class.new(GraphQL::Schema) do
74
+ self.query(query)
75
+ self.mutation(mutation)
76
+ self.cursor_encoder(PassThroughEncoder)
77
+ end
74
78
  }
75
79
 
76
80
 
@@ -117,6 +117,13 @@ describe GraphQL::Relay::RelationConnection do
117
117
  assert_equal true, get_page_info(result)["hasNextPage"]
118
118
  assert_equal true, get_page_info(result)["hasPreviousPage"]
119
119
 
120
+ last_cursor = get_last_cursor(result)
121
+ result = with_bidirectional_pagination {
122
+ star_wars_query(query_string, "last" => 1, "before" => last_cursor)
123
+ }
124
+ assert_equal true, get_page_info(result)["hasNextPage"]
125
+ assert_equal false, get_page_info(result)["hasPreviousPage"]
126
+
120
127
  result = star_wars_query(query_string, "first" => 100)
121
128
  last_cursor = get_last_cursor(result)
122
129
 
@@ -129,7 +136,6 @@ describe GraphQL::Relay::RelationConnection do
129
136
  }
130
137
  assert_equal true, get_page_info(result)["hasNextPage"]
131
138
  assert_equal true, get_page_info(result)["hasPreviousPage"]
132
-
133
139
  end
134
140
 
135
141
  it 'slices the result' do