graphql 0.18.4 → 0.18.5

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