graphql 0.19.0 → 0.19.1

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 (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