graphql 0.19.0 → 0.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -0
  3. data/lib/graphql/base_type.rb +0 -42
  4. data/lib/graphql/define/instance_definable.rb +27 -14
  5. data/lib/graphql/interface_type.rb +1 -2
  6. data/lib/graphql/invalid_null_error.rb +3 -2
  7. data/lib/graphql/language/lexer.rl +1 -1
  8. data/lib/graphql/language/parser.rb +544 -531
  9. data/lib/graphql/language/parser.y +1 -0
  10. data/lib/graphql/language/parser_tests.rb +1 -1
  11. data/lib/graphql/object_type.rb +0 -21
  12. data/lib/graphql/query/serial_execution/value_resolution.rb +5 -6
  13. data/lib/graphql/relay/node.rb +6 -1
  14. data/lib/graphql/schema.rb +30 -10
  15. data/lib/graphql/schema/loader.rb +5 -1
  16. data/lib/graphql/schema/validation.rb +31 -0
  17. data/lib/graphql/union_type.rb +0 -1
  18. data/lib/graphql/unresolved_type_error.rb +16 -0
  19. data/lib/graphql/version.rb +1 -1
  20. data/spec/graphql/analysis/query_complexity_spec.rb +5 -1
  21. data/spec/graphql/define/instance_definable_spec.rb +11 -0
  22. data/spec/graphql/field_spec.rb +3 -2
  23. data/spec/graphql/non_null_type_spec.rb +2 -2
  24. data/spec/graphql/query/context_spec.rb +1 -0
  25. data/spec/graphql/query/executor_spec.rb +2 -2
  26. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +49 -9
  27. data/spec/graphql/schema/loader_spec.rb +6 -1
  28. data/spec/graphql/schema/printer_spec.rb +1 -1
  29. data/spec/graphql/schema_spec.rb +56 -0
  30. data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +1 -1
  31. data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +1 -1
  32. data/spec/support/dairy_app.rb +0 -5
  33. metadata +3 -2
@@ -126,6 +126,7 @@ rule
126
126
  | UNION
127
127
  | ENUM
128
128
  | INPUT
129
+ | DIRECTIVE
129
130
 
130
131
  name_without_on:
131
132
  IDENTIFIER
@@ -33,7 +33,7 @@ module GraphQL
33
33
  }
34
34
 
35
35
  fragment moreNestedFields on NestedType @or(something: "ok") {
36
- anotherNestedField
36
+ anotherNestedField @enum(directive: true)
37
37
  }
38
38
  |}
39
39
 
@@ -74,26 +74,5 @@ module GraphQL
74
74
  memo.merge!(iface.fields)
75
75
  end
76
76
  end
77
-
78
-
79
- # Error raised when the value provided for a field can't be resolved to one of the possible types
80
- # for the field.
81
- class UnresolvedTypeError < GraphQL::Error
82
- attr_reader :field_name, :field_type, :parent_type
83
-
84
- def initialize(field_name, field_type, parent_type)
85
- @field_name = field_name
86
- @field_type = field_type
87
- @parent_type = parent_type
88
- super(exception_message)
89
- end
90
-
91
- private
92
-
93
- def exception_message
94
- "The value returned for field #{field_name} on #{parent_type} could not be resolved "\
95
- "to one of the possible types for #{field_type}."
96
- end
97
- end
98
77
  end
99
78
  end
@@ -57,12 +57,11 @@ module GraphQL
57
57
 
58
58
  class HasPossibleTypeResolution < BaseResolution
59
59
  def non_null_result
60
- # When deprecations are removed:
61
- # resolved_type = execution_context.schema.resolve_type(value)
62
- resolved_type = field_type.legacy_resolve_type(value, execution_context)
60
+ resolved_type = execution_context.schema.resolve_type(value, execution_context.query.context)
61
+ possible_types = execution_context.schema.possible_types(field_type)
63
62
 
64
- unless resolved_type.is_a?(GraphQL::ObjectType)
65
- raise GraphQL::ObjectType::UnresolvedTypeError.new(irep_node.definition_name, field_type, parent_type)
63
+ unless resolved_type.is_a?(GraphQL::ObjectType) && possible_types.include?(resolved_type)
64
+ raise GraphQL::UnresolvedTypeError.new(irep_node.definition_name, execution_context.schema, field_type, parent_type, resolved_type)
66
65
  end
67
66
 
68
67
  strategy_class = get_strategy_for_kind(resolved_type.kind)
@@ -87,7 +86,7 @@ module GraphQL
87
86
  # Get the "wrapped" type and resolve the value according to that type
88
87
  def result
89
88
  if value.nil? || value.is_a?(GraphQL::ExecutionError)
90
- raise GraphQL::InvalidNullError.new(irep_node.definition_name, value)
89
+ raise GraphQL::InvalidNullError.new(parent_type.name, irep_node.definition_name, value)
91
90
  else
92
91
  wrapped_type = field_type.of_type
93
92
  strategy_class = get_strategy_for_kind(wrapped_type.kind)
@@ -7,12 +7,17 @@ module GraphQL
7
7
  # We have to define it fresh each time because
8
8
  # its name will be modified and its description
9
9
  # _may_ be modified.
10
- GraphQL::Field.define do
10
+ node_field = GraphQL::Field.define do
11
11
  type(GraphQL::Relay::Node.interface)
12
12
  description("Fetches an object given its ID")
13
13
  argument(:id, !types.ID, "ID of the object")
14
14
  resolve(GraphQL::Relay::Node::FindNode)
15
15
  end
16
+
17
+ # This is used to identify generated fields in the schema
18
+ node_field.metadata[:relay_node_field] = true
19
+
20
+ node_field
16
21
  end
17
22
 
18
23
  # @return [GraphQL::InterfaceType] The interface which all Relay types must implement
@@ -63,11 +63,8 @@ module GraphQL
63
63
 
64
64
  DIRECTIVES = [GraphQL::Directive::SkipDirective, GraphQL::Directive::IncludeDirective, GraphQL::Directive::DeprecatedDirective]
65
65
  DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
66
- RESOLVE_TYPE_PROC_REQUIRED = -> (obj, ctx) { raise(NotImplementedError, "Schema.resolve_type is undefined, can't resolve type for #{obj.inspect}") }
67
- OBJECT_FROM_ID_PROC_REQUIRED = -> (id, ctx) { raise(NotImplementedError, "Schema.object_from_id is undefined, can't fetch object for #{id.inspect}") }
68
- ID_FROM_OBJECT_PROC_REQUIRED = -> (obj, type, ctx) { raise(NotImplementedError, "Schema.id_from_object is undefined, can't return an ID for #{obj.inspect}") }
69
66
 
70
- attr_reader :directives, :static_validator, :object_from_id_proc, :id_from_object_proc
67
+ attr_reader :directives, :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc
71
68
 
72
69
  # @!attribute [r] middleware
73
70
  # @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution
@@ -92,9 +89,9 @@ module GraphQL
92
89
  @rescue_middleware = GraphQL::Schema::RescueMiddleware.new
93
90
  @middleware = [@rescue_middleware]
94
91
  @query_analyzers = []
95
- @resolve_type_proc = RESOLVE_TYPE_PROC_REQUIRED
96
- @object_from_id_proc = OBJECT_FROM_ID_PROC_REQUIRED
97
- @id_from_object_proc = ID_FROM_OBJECT_PROC_REQUIRED
92
+ @resolve_type_proc = nil
93
+ @object_from_id_proc = nil
94
+ @id_from_object_proc = nil
98
95
  # Default to the built-in execution strategy:
99
96
  @query_execution_strategy = GraphQL::Query::SerialExecution
100
97
  @mutation_execution_strategy = GraphQL::Query::SerialExecution
@@ -111,6 +108,15 @@ module GraphQL
111
108
  @rescue_middleware.remove_handler(*args, &block)
112
109
  end
113
110
 
111
+ def define(**kwargs, &block)
112
+ super
113
+ types
114
+ # Assert that all necessary configs are present:
115
+ validation_error = Validation.validate(self)
116
+ validation_error && raise(NotImplementedError, validation_error)
117
+ nil
118
+ end
119
+
114
120
  # @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
115
121
  def types
116
122
  @types ||= begin
@@ -193,6 +199,11 @@ module GraphQL
193
199
  # @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL
194
200
  def resolve_type(object, ctx)
195
201
  ensure_defined
202
+
203
+ if @resolve_type_proc.nil?
204
+ raise(NotImplementedError, "Can't determine GraphQL type for: #{object.inspect}, define `resolve_type (obj, ctx) -> { ... }` inside `Schema.define`.")
205
+ end
206
+
196
207
  type_result = @resolve_type_proc.call(object, ctx)
197
208
  if type_result.nil?
198
209
  nil
@@ -215,7 +226,11 @@ module GraphQL
215
226
  # @return [Any] The application object identified by `id`
216
227
  def object_from_id(id, ctx)
217
228
  ensure_defined
218
- @object_from_id_proc.call(id, ctx)
229
+ if @object_from_id_proc.nil?
230
+ raise(NotImplementedError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined")
231
+ else
232
+ @object_from_id_proc.call(id, ctx)
233
+ end
219
234
  end
220
235
 
221
236
  # @param new_proc [#call] A new callable for fetching objects by ID
@@ -226,11 +241,16 @@ module GraphQL
226
241
 
227
242
  # Get a unique identifier from this object
228
243
  # @param object [Any] An application object
244
+ # @param type [GraphQL::BaseType] The current type definition
229
245
  # @param ctx [GraphQL::Query::Context] the context for the current query
230
246
  # @return [String] a unique identifier for `object` which clients can use to refetch it
231
- def id_from_object(type, object, ctx)
247
+ def id_from_object(object, type, ctx)
232
248
  ensure_defined
233
- @id_from_object_proc.call(type, object, ctx)
249
+ if @id_from_object_proc.nil?
250
+ raise(NotImplementedError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined")
251
+ else
252
+ @id_from_object_proc.call(object, type, ctx)
253
+ end
234
254
  end
235
255
 
236
256
  # @param new_proc [#call] A new callable for generating unique IDs
@@ -22,7 +22,7 @@ module GraphQL
22
22
  types[type_object.name] = type_object
23
23
  end
24
24
 
25
- kargs = { :orphan_types => types.values }
25
+ kargs = { orphan_types: types.values, resolve_type: NullResolveType }
26
26
  [:query, :mutation, :subscription].each do |root|
27
27
  type = schema["#{root}Type"]
28
28
  kargs[root] = types.fetch(type.fetch("name")) if type
@@ -31,6 +31,10 @@ module GraphQL
31
31
  Schema.define(**kargs)
32
32
  end
33
33
 
34
+ NullResolveType = -> (obj, ctx) {
35
+ raise(NotImplementedError, "This schema was loaded from string, so it can't resolve types for objects")
36
+ }
37
+
34
38
  class << self
35
39
  private
36
40
 
@@ -125,6 +125,32 @@ module GraphQL
125
125
  "type must be a valid input type (Scalar or InputObject), not #{outer_type.class} (#{outer_type})"
126
126
  end
127
127
  }
128
+
129
+ SCHEMA_CAN_RESOLVE_TYPES = -> (schema) {
130
+ if schema.types.values.any? { |type| type.kind.resolves? } && schema.resolve_type_proc.nil?
131
+ "schema contains Interfaces or Unions, so you must define a `resolve_type (obj, ctx) -> { ... }` function"
132
+ else
133
+ # :+1:
134
+ end
135
+ }
136
+
137
+ SCHEMA_CAN_FETCH_IDS = -> (schema) {
138
+ has_node_field = schema.query && schema.query.all_fields.any? { |f| f.metadata[:relay_node_field] }
139
+ if has_node_field && schema.object_from_id_proc.nil?
140
+ "schema contains `node(id:...)` field, so you must define a `object_from_id (id, ctx) -> { ... }` function"
141
+ else
142
+ # :rocket:
143
+ end
144
+ }
145
+
146
+ SCHEMA_CAN_GENERATE_IDS = -> (schema) {
147
+ has_id_field = schema.types.values.any? { |t| t.kind.fields? && t.all_fields.any? { |f| f.resolve_proc.is_a?(GraphQL::Relay::GlobalIdResolve) } }
148
+ if has_id_field && schema.id_from_object_proc.nil?
149
+ "schema contains `global_id_field`, so you must define a `id_from_object (obj, type, ctx) -> { ... }` function"
150
+ else
151
+ # :ok_hand:
152
+ end
153
+ }
128
154
  end
129
155
 
130
156
  # A mapping of `{Class => [Proc, Proc...]}` pairs.
@@ -165,6 +191,11 @@ module GraphQL
165
191
  GraphQL::InterfaceType => [
166
192
  Rules::FIELDS_ARE_VALID,
167
193
  ],
194
+ GraphQL::Schema => [
195
+ Rules::SCHEMA_CAN_RESOLVE_TYPES,
196
+ Rules::SCHEMA_CAN_FETCH_IDS,
197
+ Rules::SCHEMA_CAN_GENERATE_IDS,
198
+ ],
168
199
  }
169
200
  end
170
201
  end
@@ -23,7 +23,6 @@ module GraphQL
23
23
  # }
24
24
  #
25
25
  class UnionType < GraphQL::BaseType
26
- include GraphQL::BaseType::HasPossibleTypes
27
26
  accepts_definitions :possible_types, :resolve_type
28
27
 
29
28
  def kind
@@ -0,0 +1,16 @@
1
+ module GraphQL
2
+ # Error raised when the value provided for a field can't be resolved to one of the possible types
3
+ # for the field.
4
+ class UnresolvedTypeError < GraphQL::Error
5
+ def initialize(field_name, schema, field_type, parent_type, received_type)
6
+ possible_types = field_type.kind.resolves? ? schema.possible_types(field_type) : [field_type]
7
+ message = %|The value from "#{field_name}" on "#{parent_type}" could not be resolved to "#{field_type}". (Received: #{received_type.inspect}, Expected: [#{possible_types.map(&:inspect).join(", ")}])|
8
+ super(message)
9
+ end
10
+ end
11
+
12
+ class ObjectType
13
+ # @deprecated Use {GraphQL::UnresolvedTypeError} instead
14
+ UnresolvedTypeError = GraphQL::UnresolvedTypeError
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.19.0"
2
+ VERSION = "0.19.1"
3
3
  end
@@ -255,7 +255,11 @@ describe GraphQL::Analysis::QueryComplexity do
255
255
  end
256
256
  end
257
257
 
258
- GraphQL::Schema.define(query: query_type, orphan_types: [double_complexity_type])
258
+ GraphQL::Schema.define(
259
+ query: query_type,
260
+ orphan_types: [double_complexity_type],
261
+ resolve_type: :pass
262
+ )
259
263
  }
260
264
  let(:query_string) {%|
261
265
  {
@@ -62,6 +62,17 @@ describe GraphQL::Define::InstanceDefinable do
62
62
  end
63
63
  end
64
64
 
65
+ describe "#define" do
66
+ it "applies new definitions to an object" do
67
+ okra = Garden::Vegetable.define(name: "Okra", plant_between: Date.new(2000, 5, 1)..Date.new(2000, 7, 1))
68
+ assert_equal "Okra", okra.name
69
+ okra.define(name: "Gumbo")
70
+ assert_equal "Gumbo", okra.name
71
+ okra.define { name "Okra" }
72
+ assert_equal "Okra", okra.name
73
+ end
74
+ end
75
+
65
76
  describe "#metadata" do
66
77
  it "gets values from definitions" do
67
78
  arugula = Garden::Vegetable.define(name: "Arugula", color: :green)
@@ -88,9 +88,10 @@ describe GraphQL::Field do
88
88
  invalid_field.name = :symbol_name
89
89
 
90
90
  dummy_query.fields["symbol_name"] = invalid_field
91
- dummy_schema = GraphQL::Schema.define(query: dummy_query)
92
91
 
93
- err = assert_raises(GraphQL::Schema::InvalidTypeError) { dummy_schema.types }
92
+ err = assert_raises(GraphQL::Schema::InvalidTypeError) {
93
+ GraphQL::Schema.define(query: dummy_query)
94
+ }
94
95
  assert_equal "QueryType is invalid: field :symbol_name name must return String, not Symbol (:symbol_name)", err.message
95
96
  end
96
97
  end
@@ -6,7 +6,7 @@ describe GraphQL::NonNullType do
6
6
  query_string = %|{ cow { name cantBeNullButIs } }|
7
7
  result = DummySchema.execute(query_string)
8
8
  assert_equal({"cow" => nil }, result["data"])
9
- assert_equal([{"message"=>"Cannot return null for non-nullable field cantBeNullButIs"}], result["errors"])
9
+ assert_equal([{"message"=>"Cannot return null for non-nullable field Cow.cantBeNullButIs"}], result["errors"])
10
10
  end
11
11
 
12
12
  it "propagates the null up to the next nullable field" do
@@ -25,7 +25,7 @@ describe GraphQL::NonNullType do
25
25
  |
26
26
  result = DummySchema.execute(query_string)
27
27
  assert_equal(nil, result["data"])
28
- assert_equal([{"message"=>"Cannot return null for non-nullable field nonNullInt"}], result["errors"])
28
+ assert_equal([{"message"=>"Cannot return null for non-nullable field DeepNonNull.nonNullInt"}], result["errors"])
29
29
  end
30
30
  end
31
31
  end
@@ -2,6 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe GraphQL::Query::Context do
4
4
  let(:query_type) { GraphQL::ObjectType.define {
5
+ name "Query"
5
6
  field :context, types.String do
6
7
  argument :key, !types.String
7
8
  resolve -> (target, args, ctx) { ctx[args[:key]] }
@@ -85,7 +85,7 @@ describe GraphQL::Query::Executor do
85
85
  end
86
86
  end
87
87
 
88
- GraphQL::Schema.define(query: DummyQueryType, mutation: MutationType)
88
+ GraphQL::Schema.define(query: DummyQueryType, mutation: MutationType, resolve_type: :pass, id_from_object: :pass)
89
89
  }
90
90
  let(:variables) { nil }
91
91
  let(:query_string) { %|
@@ -127,7 +127,7 @@ describe GraphQL::Query::Executor do
127
127
  "data" => { "cow" => nil },
128
128
  "errors" => [
129
129
  {
130
- "message" => "Cannot return null for non-nullable field cantBeNullButIs"
130
+ "message" => "Cannot return null for non-nullable field Cow.cantBeNullButIs"
131
131
  }
132
132
  ]
133
133
  }
@@ -18,20 +18,41 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
18
18
  field :someField, !types.Int
19
19
  end
20
20
 
21
+ some_object = GraphQL::ObjectType.define do
22
+ name "SomeObject"
23
+ interfaces [interface]
24
+ end
25
+
26
+ other_object = GraphQL::ObjectType.define do
27
+ name "OtherObject"
28
+ end
29
+
30
+ OtherObject = Class.new
31
+
21
32
  query_root = GraphQL::ObjectType.define do
22
33
  name "Query"
23
34
  field :tomorrow, day_of_week_enum do
24
35
  argument :today, day_of_week_enum
25
36
  resolve ->(obj, args, ctx) { (args["today"] + 1) % 7 }
26
37
  end
27
- field :misbehavedInterface, interface do
38
+ field :resolvesToNilInterface, interface do
28
39
  resolve ->(obj, args, ctx) { Object.new }
29
40
  end
41
+ field :resolvesToWrongTypeInterface, interface do
42
+ resolve ->(obj, args, ctx) { OtherObject.new }
43
+ end
30
44
  end
31
45
 
32
46
  GraphQL::Schema.define do
33
47
  query(query_root)
34
- resolve_type -> (obj, ctx) { nil }
48
+ orphan_types [some_object]
49
+ resolve_type -> (obj, ctx) do
50
+ if obj.is_a?(OtherObject)
51
+ other_object
52
+ else
53
+ nil
54
+ end
55
+ end
35
56
  end
36
57
  }
37
58
 
@@ -57,14 +78,33 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
57
78
  end
58
79
 
59
80
  describe "interface type resolution" do
60
- let(:query_string) { %|
61
- {
62
- misbehavedInterface { someField }
63
- }
64
- |}
81
+ describe "when type can't be resolved" do
82
+ let(:query_string) { %|
83
+ {
84
+ resolvesToNilInterface { someField }
85
+ }
86
+ |}
65
87
 
66
- it "raises an error if it cannot resolve the type of an interface" do
67
- assert_raises(GraphQL::ObjectType::UnresolvedTypeError) { result }
88
+ it "raises an error" do
89
+ err = assert_raises(GraphQL::ObjectType::UnresolvedTypeError) { result }
90
+ expected_message = %|The value from "resolvesToNilInterface" on "Query" could not be resolved to "SomeInterface". (Received: nil, Expected: [SomeObject])|
91
+ assert_equal expected_message, err.message
92
+ end
68
93
  end
94
+
95
+ describe "when type resolves but is not a possible type of an interface" do
96
+ let(:query_string) { %|
97
+ {
98
+ resolvesToWrongTypeInterface { someField }
99
+ }
100
+ |}
101
+
102
+ it "raises an error" do
103
+ err = assert_raises(GraphQL::ObjectType::UnresolvedTypeError) { result }
104
+ expected_message = %|The value from "resolvesToWrongTypeInterface" on "Query" could not be resolved to "SomeInterface". (Received: OtherObject, Expected: [SomeObject])|
105
+ assert_equal expected_message, err.message
106
+ end
107
+ end
108
+
69
109
  end
70
110
  end