graphql 0.18.4 → 0.18.5

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -2
  3. data/lib/graphql/argument.rb +2 -2
  4. data/lib/graphql/base_type.rb +17 -0
  5. data/lib/graphql/define.rb +1 -1
  6. data/lib/graphql/directive.rb +6 -0
  7. data/lib/graphql/enum_type.rb +48 -4
  8. data/lib/graphql/field.rb +81 -19
  9. data/lib/graphql/field/resolve.rb +1 -1
  10. data/lib/graphql/input_object_type.rb +17 -3
  11. data/lib/graphql/interface_type.rb +12 -2
  12. data/lib/graphql/list_type.rb +23 -4
  13. data/lib/graphql/non_null_type.rb +27 -2
  14. data/lib/graphql/query.rb +0 -1
  15. data/lib/graphql/query/arguments.rb +1 -1
  16. data/lib/graphql/query/serial_execution/value_resolution.rb +3 -1
  17. data/lib/graphql/relay/global_node_identification.rb +29 -16
  18. data/lib/graphql/scalar_type.rb +24 -1
  19. data/lib/graphql/schema.rb +109 -19
  20. data/lib/graphql/schema/printer.rb +3 -3
  21. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  22. data/lib/graphql/union_type.rb +19 -6
  23. data/lib/graphql/version.rb +1 -1
  24. data/readme.md +5 -6
  25. data/spec/graphql/analysis/query_complexity_spec.rb +1 -1
  26. data/spec/graphql/argument_spec.rb +1 -1
  27. data/spec/graphql/field_spec.rb +1 -1
  28. data/spec/graphql/interface_type_spec.rb +0 -19
  29. data/spec/graphql/query/context_spec.rb +1 -1
  30. data/spec/graphql/query/executor_spec.rb +1 -1
  31. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +8 -4
  32. data/spec/graphql/relay/array_connection_spec.rb +17 -17
  33. data/spec/graphql/relay/connection_type_spec.rb +1 -1
  34. data/spec/graphql/relay/global_node_identification_spec.rb +3 -21
  35. data/spec/graphql/relay/mutation_spec.rb +2 -2
  36. data/spec/graphql/relay/page_info_spec.rb +9 -9
  37. data/spec/graphql/relay/relation_connection_spec.rb +31 -31
  38. data/spec/graphql/schema/printer_spec.rb +1 -1
  39. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  40. data/spec/graphql/schema/timeout_middleware_spec.rb +1 -1
  41. data/spec/graphql/schema_spec.rb +18 -0
  42. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +6 -6
  43. data/spec/graphql/union_type_spec.rb +0 -4
  44. data/spec/spec_helper.rb +1 -1
  45. data/spec/support/dairy_app.rb +13 -8
  46. data/spec/support/star_wars_schema.rb +18 -16
  47. metadata +2 -2
data/lib/graphql/query.rb CHANGED
@@ -50,7 +50,6 @@ module GraphQL
50
50
  @operations = {}
51
51
  @provided_variables = variables
52
52
  @query_string = query_string
53
-
54
53
  @document = document || GraphQL.parse(query_string)
55
54
  @document.definitions.each do |part|
56
55
  if part.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
@@ -14,7 +14,7 @@ module GraphQL
14
14
  end
15
15
  end
16
16
 
17
- # @param [String, Symbol] name or index of value to access
17
+ # @param key [String, Symbol] name or index of value to access
18
18
  # @return [Object] the argument at that key
19
19
  def [](key)
20
20
  @argument_values[key.to_s]
@@ -54,7 +54,9 @@ module GraphQL
54
54
 
55
55
  class HasPossibleTypeResolution < BaseResolution
56
56
  def non_null_result
57
- resolved_type = field_type.resolve_type(value, execution_context)
57
+ # When deprecations are removed:
58
+ # resolved_type = execution_context.schema.resolve_type(value)
59
+ resolved_type = field_type.legacy_resolve_type(value, execution_context)
58
60
 
59
61
  unless resolved_type.is_a?(GraphQL::ObjectType)
60
62
  raise GraphQL::ObjectType::UnresolvedTypeError.new(irep_node.definition_name, field_type, parent_type)
@@ -12,6 +12,10 @@ module GraphQL
12
12
  accepts_definitions(:object_from_id, :type_from_object, :to_global_id, :from_global_id, :description)
13
13
  lazy_defined_attr_accessor :description
14
14
 
15
+ # Memoize the schema to support deprecated node_ident-level resolve functions
16
+ # TODO: remove after Schema.resolve_type is required
17
+ attr_accessor :schema
18
+
15
19
  class << self
16
20
  attr_accessor :id_separator
17
21
  end
@@ -28,12 +32,20 @@ module GraphQL
28
32
  @interface ||= begin
29
33
  ensure_defined
30
34
  ident = self
31
- GraphQL::InterfaceType.define do
32
- name "Node"
33
- field :id, !types.ID
34
- resolve_type -> (obj, schema) {
35
- ident.type_from_object(obj)
36
- }
35
+ if @type_from_object_proc
36
+ # TODO: remove after Schema.resolve_type is required
37
+ GraphQL::InterfaceType.define do
38
+ name "Node"
39
+ field :id, !types.ID
40
+ resolve_type -> (obj, ctx) {
41
+ ident.type_from_object(obj)
42
+ }
43
+ end
44
+ else
45
+ GraphQL::InterfaceType.define do
46
+ name "Node"
47
+ field :id, !types.ID
48
+ end
37
49
  end
38
50
  end
39
51
  end
@@ -90,22 +102,23 @@ module GraphQL
90
102
 
91
103
  # Use the provided config to
92
104
  # get a type for a given object
105
+ # TODO: remove after Schema.resolve_type is required
93
106
  def type_from_object(object)
94
107
  ensure_defined
95
- type_result = @type_from_object_proc.call(object)
96
- if type_result.nil?
97
- nil
98
- elsif !type_result.is_a?(GraphQL::BaseType)
99
- type_str = "#{type_result} (#{type_result.class.name})"
100
- raise "type_from_object(#{object}) returned #{type_str}, but it should return a GraphQL type"
101
- else
102
- type_result
108
+ warn("type_from_object(object) is deprecated; use Schema.resolve_type(object) instead")
109
+
110
+ if @type_from_object_proc
111
+ schema.resolve_type = @type_from_object_proc
112
+ @type_from_object_proc = nil
103
113
  end
114
+
115
+ schema.resolve_type(object)
104
116
  end
105
117
 
106
- def type_from_object=(proc)
118
+ def type_from_object=(new_type_from_object_proc)
107
119
  ensure_defined
108
- @type_from_object_proc = proc
120
+ warn("type_from_object(object) is deprecated; use Schema.resolve_type(object) instead")
121
+ @type_from_object_proc = new_type_from_object_proc
109
122
  end
110
123
 
111
124
  # Use the provided config to
@@ -1,5 +1,28 @@
1
1
  module GraphQL
2
- # The parent type for scalars, eg {GraphQL::STRING_TYPE}, {GraphQL::INT_TYPE}
2
+ # # GraphQL::ScalarType
3
+ #
4
+ # Scalars are plain values. They are leaf nodes in a GraphQL query tree.
5
+ #
6
+ # ## Built-in Scalars
7
+ #
8
+ # `GraphQL` comes with standard built-in scalars:
9
+ #
10
+ # |Constant | `.define` helper|
11
+ # |-------|--------|
12
+ # |`GraphQL::STRING_TYPE` | `types.String`|
13
+ # |`GraphQL::INT_TYPE` | `types.Int`|
14
+ # |`GraphQL::FLOAT_TYPE` | `types.Float`|
15
+ # |`GraphQL::ID_TYPE` | `types.ID`|
16
+ # |`GraphQL::BOOLEAN_TYPE` | `types.Boolean`|
17
+ #
18
+ # (`types` is an instance of `GraphQL::Definition::TypeDefiner`; `.String`, `.Float`, etc are methods which return built-in scalars.)
19
+ #
20
+ # ## Custom Scalars
21
+ #
22
+ # You can define custom scalars for your GraphQL server. It requires some special functions:
23
+ #
24
+ # - `coerce_input` is used to prepare incoming values for GraphQL execution. (Incoming values come from variables or literal values in the query string.)
25
+ # - `coerce_result` is used to turn Ruby values _back_ into serializable values for query responses.
3
26
  #
4
27
  # @example defining a type for Time
5
28
  # TimeType = GraphQL::ScalarType.define do
@@ -11,33 +11,88 @@ require "graphql/schema/validation"
11
11
 
12
12
  module GraphQL
13
13
  # A GraphQL schema which may be queried with {GraphQL::Query}.
14
+ #
15
+ # The {Schema} contains:
16
+ #
17
+ # - types for exposing your application
18
+ # - query analyzers for assessing incoming queries (including max depth & max complexity restrictions)
19
+ # - execution strategies for running incoming queries
20
+ # - middleware for interacting with execution
21
+ #
22
+ # Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}.
23
+ # The schema will traverse the tree of fields & types, using those as starting points.
24
+ # Any undiscoverable types may be provided with the `types` configuration.
25
+ #
26
+ # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
27
+ # (These configurations can be overridden by specific calls to {Schema#execute})
28
+ #
29
+ # Schemas can specify how queries should be executed against them.
30
+ # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy`
31
+ # each apply to corresponding root types.
32
+ #
33
+ # A schema accepts a `Relay::GlobalNodeIdentification` instance for use with Relay IDs.
34
+ #
35
+ # @example defining a schema
36
+ # MySchema = GraphQL::Schema.define do
37
+ # query QueryType
38
+ # middleware PermissionMiddleware
39
+ # rescue_from(ActiveRecord::RecordNotFound) { "Not found" }
40
+ # # If types are only connected by way of interfaces, they must be added here
41
+ # orphan_types ImageType, AudioType
42
+ # end
43
+ #
14
44
  class Schema
15
- extend Forwardable
45
+ include GraphQL::Define::InstanceDefinable
46
+ accepts_definitions \
47
+ :query, :mutation, :subscription,
48
+ :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
49
+ :max_depth, :max_complexity,
50
+ :node_identification,
51
+ :orphan_types, :resolve_type,
52
+ query_analyzer: -> (schema, analyzer) { schema.query_analyzers << analyzer },
53
+ middleware: -> (schema, middleware) { schema.middleware << middleware },
54
+ rescue_from: -> (schema, err_class, &block) { schema.rescue_from(err_class, &block)}
55
+
56
+ lazy_defined_attr_accessor \
57
+ :query, :mutation, :subscription,
58
+ :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
59
+ :max_depth, :max_complexity,
60
+ :orphan_types,
61
+ :query_analyzers, :middleware
62
+
16
63
 
17
64
  DIRECTIVES = [GraphQL::Directive::SkipDirective, GraphQL::Directive::IncludeDirective]
18
65
  DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
66
+ RESOLVE_TYPE_PROC_REQUIRED = -> (obj) { raise("Schema.resolve_type is undefined, can't resolve type for #{obj}") }
19
67
 
20
- attr_reader :query, :mutation, :subscription, :directives, :static_validator, :query_analyzers
21
- attr_accessor :max_depth
22
- attr_accessor :max_complexity
23
-
24
- # Override these if you don't want the default executor:
25
- attr_accessor :query_execution_strategy,
26
- :mutation_execution_strategy,
27
- :subscription_execution_strategy
68
+ attr_reader :directives, :static_validator
28
69
 
29
70
  # @return [GraphQL::Relay::GlobalNodeIdentification] the node identification instance for this schema, when using Relay
30
- attr_accessor :node_identification
71
+ def node_identification
72
+ ensure_defined
73
+ @node_identification
74
+ end
75
+
76
+ def node_identification=(new_node_ident)
77
+ new_node_ident.schema = self
78
+ @node_identification = new_node_ident
79
+ end
31
80
 
32
81
  # @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution
33
- attr_reader :middleware
82
+ def middleware
83
+ ensure_defined
84
+ @middleware
85
+ end
34
86
 
35
87
  # @param query [GraphQL::ObjectType] the query root for the schema
36
88
  # @param mutation [GraphQL::ObjectType] the mutation root for the schema
37
89
  # @param subscription [GraphQL::ObjectType] the subscription root for the schema
38
90
  # @param max_depth [Integer] maximum query nesting (if it's greater, raise an error)
39
91
  # @param types [Array<GraphQL::BaseType>] additional types to include in this schema
40
- def initialize(query:, mutation: nil, subscription: nil, max_depth: nil, max_complexity: nil, types: [])
92
+ def initialize(query: nil, mutation: nil, subscription: nil, max_depth: nil, max_complexity: nil, types: [])
93
+ if query
94
+ warn("Schema.new is deprecated, use Schema.define instead")
95
+ end
41
96
  @query = query
42
97
  @mutation = mutation
43
98
  @subscription = subscription
@@ -49,18 +104,28 @@ module GraphQL
49
104
  @rescue_middleware = GraphQL::Schema::RescueMiddleware.new
50
105
  @middleware = [@rescue_middleware]
51
106
  @query_analyzers = []
107
+ @resolve_type_proc = RESOLVE_TYPE_PROC_REQUIRED
52
108
  # Default to the built-in execution strategy:
53
- self.query_execution_strategy = GraphQL::Query::SerialExecution
54
- self.mutation_execution_strategy = GraphQL::Query::SerialExecution
55
- self.subscription_execution_strategy = GraphQL::Query::SerialExecution
109
+ @query_execution_strategy = GraphQL::Query::SerialExecution
110
+ @mutation_execution_strategy = GraphQL::Query::SerialExecution
111
+ @subscription_execution_strategy = GraphQL::Query::SerialExecution
112
+ end
113
+
114
+ def rescue_from(*args, &block)
115
+ ensure_defined
116
+ @rescue_middleware.rescue_from(*args, &block)
56
117
  end
57
118
 
58
- def_delegators :@rescue_middleware, :rescue_from, :remove_handler
119
+ def remove_handler(*args, &block)
120
+ ensure_defined
121
+ @rescue_middleware.remove_handler(*args, &block)
122
+ end
59
123
 
60
124
  # @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
61
125
  def types
62
126
  @types ||= begin
63
- all_types = @orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
127
+ ensure_defined
128
+ all_types = orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
64
129
  GraphQL::Schema::ReduceTypes.reduce(all_types.compact)
65
130
  end
66
131
  end
@@ -69,13 +134,14 @@ module GraphQL
69
134
  # See {Query#initialize} for arguments.
70
135
  # @return [Hash] query result, ready to be serialized as JSON
71
136
  def execute(*args)
72
- query = GraphQL::Query.new(self, *args)
73
- query.result
137
+ query_obj = GraphQL::Query.new(self, *args)
138
+ query_obj.result
74
139
  end
75
140
 
76
141
  # Resolve field named `field_name` for type `parent_type`.
77
142
  # Handles dynamic fields `__typename`, `__type` and `__schema`, too
78
143
  def get_field(parent_type, field_name)
144
+ ensure_defined
79
145
  defined_field = parent_type.get_field(field_name)
80
146
  if defined_field
81
147
  defined_field
@@ -91,12 +157,15 @@ module GraphQL
91
157
  end
92
158
 
93
159
  def type_from_ast(ast_node)
160
+ ensure_defined
94
161
  GraphQL::Schema::TypeExpression.build_type(self, ast_node)
95
162
  end
96
163
 
164
+ # TODO: when `resolve_type` is schema level, can this be removed?
97
165
  # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
98
166
  # @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
99
167
  def possible_types(type_defn)
168
+ ensure_defined
100
169
  @interface_possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
101
170
  @interface_possible_types.possible_types(type_defn)
102
171
  end
@@ -126,5 +195,26 @@ module GraphQL
126
195
  raise ArgumentError, "unknown operation type: #{operation}"
127
196
  end
128
197
  end
198
+
199
+ # Determine the GraphQL type for a given object.
200
+ # This is required for unions and interfaces (include Relay's node interface)
201
+ # @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL
202
+ def resolve_type(object, ctx)
203
+ ensure_defined
204
+ type_result = @resolve_type_proc.call(object, ctx)
205
+ if type_result.nil?
206
+ nil
207
+ elsif !type_result.is_a?(GraphQL::BaseType)
208
+ type_str = "#{type_result} (#{type_result.class.name})"
209
+ raise "resolve_type(#{object}) returned #{type_str}, but it should return a GraphQL type"
210
+ else
211
+ type_result
212
+ end
213
+ end
214
+
215
+ def resolve_type=(new_resolve_type_proc)
216
+ ensure_defined
217
+ @resolve_type_proc = new_resolve_type_proc
218
+ end
129
219
  end
130
220
  end
@@ -3,8 +3,8 @@ module GraphQL
3
3
  # Used to convert your {GraphQL::Schema} to a GraphQL schema string
4
4
  #
5
5
  # @example print your schema to standard output
6
- # Schema = GraphQL::Schema.new(query: QueryType)
7
- # puts GraphQL::Schema::Printer.print_schema(Schema)
6
+ # MySchema = GraphQL::Schema.define(query: QueryType)
7
+ # puts GraphQL::Schema::Printer.print_schema(MySchema)
8
8
  #
9
9
  module Printer
10
10
  extend self
@@ -20,7 +20,7 @@ module GraphQL
20
20
  query_root = ObjectType.define do
21
21
  name "Query"
22
22
  end
23
- schema = Schema.new(query: query_root)
23
+ schema = GraphQL::Schema.define(query: query_root)
24
24
  print_filtered_schema(schema, method(:is_introspection_type))
25
25
  end
26
26
 
@@ -10,7 +10,7 @@ module GraphQL
10
10
  if !valid
11
11
  kind_of_node = node_type(parent)
12
12
  error_arg_name = parent_name(parent, defn)
13
- context.errors << message("Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value", parent, context: context)
13
+ context.errors << message("Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value. Expected type '#{arg_defn.type}'.", parent, context: context)
14
14
  end
15
15
  end
16
16
  end
@@ -1,14 +1,27 @@
1
1
  module GraphQL
2
- # A collection of {ObjectType}s
2
+ # A Union is is a collection of object types which may appear in the same place.
3
3
  #
4
- # @example a union of types
4
+ # The members of a union are declared with `possible_types`.
5
5
  #
6
- # PetUnion = GraphQL::UnionType.define do
7
- # name "Pet"
8
- # description "Animals that live in your house"
9
- # possible_types [DogType, CatType, FishType]
6
+ # @example A union of object types
7
+ # MediaUnion = GraphQL::UnionType.define do
8
+ # name "Media"
9
+ # description "Media objects which you can enjoy"
10
+ # possible_types [AudioType, ImageType, VideoType]
10
11
  # end
11
12
  #
13
+ # A union itself has no fields; only its members have fields.
14
+ # So, when you query, you must use fragment spreads to access fields.
15
+ #
16
+ # @example Querying for fields on union members
17
+ # {
18
+ # searchMedia(name: "Jens Lekman") {
19
+ # ... on Audio { name, duration }
20
+ # ... on Image { name, height, width }
21
+ # ... on Video { name, length, quality }
22
+ # }
23
+ # }
24
+ #
12
25
  class UnionType < GraphQL::BaseType
13
26
  include GraphQL::BaseType::HasPossibleTypes
14
27
  accepts_definitions :possible_types, :resolve_type
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.18.4"
2
+ VERSION = "0.18.5"
3
3
  end
data/readme.md CHANGED
@@ -62,10 +62,10 @@ QueryType = GraphQL::ObjectType.define do
62
62
  end
63
63
 
64
64
  # Then create your schema
65
- Schema = GraphQL::Schema.new(
66
- query: QueryType,
67
- max_depth: 8,
68
- )
65
+ Schema = GraphQL::Schema.define do
66
+ query QueryType,
67
+ max_depth 8,
68
+ end
69
69
  ```
70
70
 
71
71
  #### Execute queries
@@ -127,10 +127,9 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
127
127
  - Reduce ad-hoc traversals?
128
128
  - Validators are order-dependent, is this a smell?
129
129
  - Tests for interference between validators are poor
130
- - Maybe this is a candidate for a rewrite? Can it somehow work more closely with query analysis? Somehow support the `Query#perform_validation` refactor?
130
+ - Maybe this is a candidate for a rewrite?
131
131
  - Add Rails-y argument validations, eg `less_than: 100`, `max_length: 255`, `one_of: [...]`
132
132
  - Must be customizable
133
- - Refactor `Query#perform_validation`, how can that be organized better?
134
133
  - Relay:
135
134
  - `GlobalNodeIdentification.to_global_id` should receive the type name and _object_, not `id`. (Or, maintain the "`type_name, id` in, `type_name, id` out" pattern?)
136
135
  - Reduce duplication in ArrayConnection / RelationConnection
@@ -255,7 +255,7 @@ describe GraphQL::Analysis::QueryComplexity do
255
255
  end
256
256
  end
257
257
 
258
- GraphQL::Schema.new(query: query_type, types: [double_complexity_type])
258
+ GraphQL::Schema.define(query: query_type, orphan_types: [double_complexity_type])
259
259
  }
260
260
  let(:query_string) {%|
261
261
  {
@@ -10,7 +10,7 @@ describe GraphQL::Argument do
10
10
  end
11
11
 
12
12
  err = assert_raises(GraphQL::Schema::InvalidTypeError) {
13
- schema = GraphQL::Schema.new(query: query_type)
13
+ schema = GraphQL::Schema.define(query: query_type)
14
14
  schema.types
15
15
  }
16
16
 
@@ -82,7 +82,7 @@ describe GraphQL::Field do
82
82
  invalid_field.name = :symbol_name
83
83
 
84
84
  dummy_query.fields["symbol_name"] = invalid_field
85
- dummy_schema = GraphQL::Schema.new(query: dummy_query)
85
+ dummy_schema = GraphQL::Schema.define(query: dummy_query)
86
86
 
87
87
  err = assert_raises(GraphQL::Schema::InvalidTypeError) { dummy_schema.types }
88
88
  assert_equal "QueryType is invalid: field :symbol_name name must return String, not Symbol (:symbol_name)", err.message