graphql 1.8.3 → 1.8.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -1
- data/lib/graphql/argument.rb +1 -0
- data/lib/graphql/authorization.rb +81 -0
- data/lib/graphql/boolean_type.rb +0 -1
- data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +2 -1
- data/lib/graphql/execution/execute.rb +34 -10
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/field.rb +7 -1
- data/lib/graphql/float_type.rb +0 -1
- data/lib/graphql/id_type.rb +0 -1
- data/lib/graphql/int_type.rb +0 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/object_type.rb +3 -3
- data/lib/graphql/query.rb +6 -0
- data/lib/graphql/query/arguments.rb +2 -0
- data/lib/graphql/query/context.rb +6 -0
- data/lib/graphql/query/variables.rb +7 -1
- data/lib/graphql/relay/connection_instrumentation.rb +2 -2
- data/lib/graphql/relay/connection_resolve.rb +7 -27
- data/lib/graphql/relay/connection_type.rb +1 -0
- data/lib/graphql/relay/edge_type.rb +1 -0
- data/lib/graphql/relay/edges_instrumentation.rb +9 -25
- data/lib/graphql/relay/mutation/instrumentation.rb +1 -2
- data/lib/graphql/relay/mutation/resolve.rb +2 -4
- data/lib/graphql/relay/node.rb +1 -6
- data/lib/graphql/relay/page_info.rb +1 -9
- data/lib/graphql/schema.rb +84 -11
- data/lib/graphql/schema/argument.rb +13 -0
- data/lib/graphql/schema/enum.rb +1 -1
- data/lib/graphql/schema/enum_value.rb +4 -0
- data/lib/graphql/schema/field.rb +44 -11
- data/lib/graphql/schema/interface.rb +20 -0
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +25 -3
- data/lib/graphql/schema/member/instrumentation.rb +15 -17
- data/lib/graphql/schema/mutation.rb +4 -0
- data/lib/graphql/schema/object.rb +33 -0
- data/lib/graphql/schema/possible_types.rb +2 -0
- data/lib/graphql/schema/resolver.rb +10 -0
- data/lib/graphql/schema/traversal.rb +9 -2
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +11 -2
- data/lib/graphql/string_type.rb +0 -1
- data/lib/graphql/types.rb +7 -0
- data/lib/graphql/types/relay.rb +31 -0
- data/lib/graphql/types/relay/base_connection.rb +87 -0
- data/lib/graphql/types/relay/base_edge.rb +51 -0
- data/lib/graphql/types/relay/base_field.rb +22 -0
- data/lib/graphql/types/relay/base_interface.rb +29 -0
- data/lib/graphql/types/relay/base_object.rb +26 -0
- data/lib/graphql/types/relay/node.rb +18 -0
- data/lib/graphql/types/relay/page_info.rb +23 -0
- data/lib/graphql/unauthorized_error.rb +20 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/authorization_spec.rb +684 -0
- data/spec/graphql/query/variables_spec.rb +20 -0
- data/spec/graphql/relay/connection_instrumentation_spec.rb +1 -1
- data/spec/graphql/schema/resolver_spec.rb +31 -0
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +52 -0
- data/spec/support/dummy/schema.rb +16 -0
- data/spec/support/star_wars/schema.rb +28 -17
- metadata +15 -2
@@ -114,6 +114,10 @@ module GraphQL
|
|
114
114
|
@object_class || (superclass.respond_to?(:object_class) ? superclass.object_class : GraphQL::Schema::Object)
|
115
115
|
end
|
116
116
|
|
117
|
+
def visible?(context)
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
117
121
|
private
|
118
122
|
|
119
123
|
# Build a subclass of {.object_class} based on `self`.
|
@@ -12,6 +12,39 @@ module GraphQL
|
|
12
12
|
# @return [GraphQL::Query::Context] the context instance for this query
|
13
13
|
attr_reader :context
|
14
14
|
|
15
|
+
class << self
|
16
|
+
# This is protected so that we can be sure callers use the public method, {.authorized_new}
|
17
|
+
# @see authorized_new to make instances
|
18
|
+
protected :new
|
19
|
+
|
20
|
+
# Make a new instance of this type _if_ the auth check passes,
|
21
|
+
# otherwise, raise an error.
|
22
|
+
#
|
23
|
+
# Probably only the framework should call this method.
|
24
|
+
#
|
25
|
+
# This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?`
|
26
|
+
# hook returns some lazy value (like a Promise).
|
27
|
+
#
|
28
|
+
# The reason that the auth check is in this wrapper method instead of {.new} is because
|
29
|
+
# of how it might return a Promise. It would be weird if `.new` returned a promise;
|
30
|
+
# It would be a headache to try to maintain Promise-y state inside a {Schema::Object}
|
31
|
+
# instance. So, hopefully this wrapper method will do the job.
|
32
|
+
#
|
33
|
+
# @param object [Object] The thing wrapped by this object
|
34
|
+
# @param context [GraphQL::Query::Context]
|
35
|
+
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
|
36
|
+
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
|
37
|
+
def authorized_new(object, context)
|
38
|
+
context.schema.after_lazy(authorized?(object, context)) do |is_authorized|
|
39
|
+
if is_authorized
|
40
|
+
self.new(object, context)
|
41
|
+
else
|
42
|
+
raise GraphQL::UnauthorizedError.new(object: object, type: self, context: context)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
15
48
|
def initialize(object, context)
|
16
49
|
@object = object
|
17
50
|
@context = context
|
@@ -99,6 +99,15 @@ module GraphQL
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
# Specifies the complexity of the field. Defaults to `1`
|
103
|
+
# @return [Integer, Proc]
|
104
|
+
def complexity(new_complexity = nil)
|
105
|
+
if new_complexity
|
106
|
+
@complexity = new_complexity
|
107
|
+
end
|
108
|
+
@complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
|
109
|
+
end
|
110
|
+
|
102
111
|
def field_options
|
103
112
|
{
|
104
113
|
type: type_expr,
|
@@ -108,6 +117,7 @@ module GraphQL
|
|
108
117
|
resolver_class: self,
|
109
118
|
arguments: arguments,
|
110
119
|
null: null,
|
120
|
+
complexity: complexity,
|
111
121
|
}
|
112
122
|
end
|
113
123
|
|
@@ -16,13 +16,20 @@ module GraphQL
|
|
16
16
|
# @return [Hash<String => Array<GraphQL::BaseType>]
|
17
17
|
attr_reader :union_memberships
|
18
18
|
|
19
|
+
|
19
20
|
# @param schema [GraphQL::Schema]
|
20
21
|
def initialize(schema, introspection: true)
|
21
22
|
@schema = schema
|
22
23
|
@introspection = introspection
|
23
24
|
@field_instrumenters =
|
24
25
|
schema.instrumenters[:field] +
|
25
|
-
|
26
|
+
# Wrap Relay-related objects in wrappers
|
27
|
+
[
|
28
|
+
GraphQL::Relay::ConnectionInstrumentation,
|
29
|
+
GraphQL::Relay::EdgesInstrumentation,
|
30
|
+
GraphQL::Relay::Mutation::Instrumentation,
|
31
|
+
GraphQL::Schema::Member::Instrumentation,
|
32
|
+
] +
|
26
33
|
schema.instrumenters[:field_after_built_ins]
|
27
34
|
|
28
35
|
# These fields have types specified by _name_,
|
@@ -158,7 +165,7 @@ Some late-bound types couldn't be resolved:
|
|
158
165
|
end
|
159
166
|
elsif !prev_type.equal?(type_defn)
|
160
167
|
# If the previous entry in the map isn't the same object we just found, raise.
|
161
|
-
raise("Duplicate type definition found for name '#{type_defn.name}' (#{prev_type.metadata[:type_class]}, #{type_defn.metadata[:type_class]}
|
168
|
+
raise("Duplicate type definition found for name '#{type_defn.name}' at '#{context_description}' (#{prev_type.metadata[:type_class] || prev_type}, #{type_defn.metadata[:type_class] || type_defn})")
|
162
169
|
end
|
163
170
|
when Class
|
164
171
|
if member.respond_to?(:graphql_definition)
|
@@ -20,8 +20,17 @@ module GraphQL
|
|
20
20
|
type = context.schema.type_from_ast(node.type)
|
21
21
|
if type.nil?
|
22
22
|
# This is handled by another validator
|
23
|
-
|
24
|
-
|
23
|
+
else
|
24
|
+
begin
|
25
|
+
valid = context.valid_literal?(value, type)
|
26
|
+
rescue GraphQL::CoercionError => err
|
27
|
+
error_message = err.message
|
28
|
+
end
|
29
|
+
|
30
|
+
if !valid
|
31
|
+
error_message ||= "Default value for $#{node.name} doesn't match type #{type}"
|
32
|
+
context.errors << message(error_message, node, context: context)
|
33
|
+
end
|
25
34
|
end
|
26
35
|
end
|
27
36
|
end
|
data/lib/graphql/string_type.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "graphql/types/relay/base_field"
|
3
|
+
require "graphql/types/relay/base_object"
|
4
|
+
require "graphql/types/relay/base_interface"
|
5
|
+
require "graphql/types/relay/page_info"
|
6
|
+
require "graphql/types/relay/base_connection"
|
7
|
+
require "graphql/types/relay/base_edge"
|
8
|
+
require "graphql/types/relay/node"
|
9
|
+
|
10
|
+
module GraphQL
|
11
|
+
module Types
|
12
|
+
# This module contains some types and fields that could support Relay conventions in GraphQL.
|
13
|
+
#
|
14
|
+
# You can use these classes out of the box if you want, but if you want to use your _own_
|
15
|
+
# GraphQL extensions along with the features in this code, you could also
|
16
|
+
# open up the source files and copy the relevant methods and configuration into
|
17
|
+
# your own classes.
|
18
|
+
#
|
19
|
+
# For example, the provided object types extend {Types::Relay::BaseObject},
|
20
|
+
# but you might want to:
|
21
|
+
#
|
22
|
+
# 1. Migrate the extensions from {Types::Relay::BaseObject} into _your app's_ base object
|
23
|
+
# 2. Copy {Relay::BaseConnection}, {Relay::BaseEdge}, etc into _your app_, and
|
24
|
+
# change them to extend _your_ base object.
|
25
|
+
#
|
26
|
+
# Similarly, `BaseField`'s extensions could be migrated to your app
|
27
|
+
# and `Node` could be implemented to mix in your base interface module.
|
28
|
+
module Relay
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
# Use this to implement Relay connections, or take it as inspiration
|
7
|
+
# for Relay classes in your own app.
|
8
|
+
#
|
9
|
+
# You may wish to copy this code into your own base class,
|
10
|
+
# so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`.
|
11
|
+
#
|
12
|
+
# @example Implementation a connection and edge
|
13
|
+
# # Given some object in your app ...
|
14
|
+
# class Types::Post < BaseObject
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # Make a couple of base classes:
|
18
|
+
# class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge; end
|
19
|
+
# class Types::BaseConnection < GraphQL::Types::Relay::BaseConnection; end
|
20
|
+
#
|
21
|
+
# # Then extend them for the object in your app
|
22
|
+
# class Types::PostEdge < Types::BaseEdge
|
23
|
+
# node_type(Types::Post)
|
24
|
+
# end
|
25
|
+
# class Types::PostConnection < Types::BaseConnection
|
26
|
+
# edge_type(Types::PostEdge)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @see Relay::BaseEdge for edge types
|
30
|
+
class BaseConnection < Types::Relay::BaseObject
|
31
|
+
extend Forwardable
|
32
|
+
def_delegators :@object, :cursor_from_node, :parent
|
33
|
+
|
34
|
+
class << self
|
35
|
+
# @return [Class]
|
36
|
+
attr_reader :node_type
|
37
|
+
|
38
|
+
# @return [Class]
|
39
|
+
attr_reader :edge_type
|
40
|
+
|
41
|
+
# Configure this connection to return `edges` and `nodes` based on `edge_type_class`.
|
42
|
+
#
|
43
|
+
# This method will use the inputs to create:
|
44
|
+
# - `edges` field
|
45
|
+
# - `nodes` field
|
46
|
+
# - description
|
47
|
+
#
|
48
|
+
# It's called when you subclass this base connection, trying to use the
|
49
|
+
# class name to set defaults. You can call it again in the class definition
|
50
|
+
# to override the default (or provide a value, if the default lookup failed).
|
51
|
+
def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type)
|
52
|
+
# Set this connection's graphql name
|
53
|
+
node_type_name = node_type.graphql_name
|
54
|
+
|
55
|
+
@node_type = node_type
|
56
|
+
@edge_type = edge_type_class
|
57
|
+
|
58
|
+
field :edges, [edge_type_class, null: true],
|
59
|
+
null: true,
|
60
|
+
description: "A list of edges.",
|
61
|
+
method: :edge_nodes,
|
62
|
+
edge_class: edge_class
|
63
|
+
|
64
|
+
field :nodes, [node_type, null: true],
|
65
|
+
null: true,
|
66
|
+
description: "A list of nodes."
|
67
|
+
|
68
|
+
description("The connection type for #{node_type_name}.")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Add the shortcut `nodes` field to this connection and its subclasses
|
72
|
+
def nodes_field
|
73
|
+
field :nodes, [@node_type, null: true], null: true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
|
78
|
+
|
79
|
+
# By default this calls through to the ConnectionWrapper's edge nodes method,
|
80
|
+
# but sometimes you need to override it to support the `nodes` field
|
81
|
+
def nodes
|
82
|
+
@object.edge_nodes
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Types
|
4
|
+
module Relay
|
5
|
+
# A class-based definition for Relay edges.
|
6
|
+
#
|
7
|
+
# Use this as a parent class in your app, or use it as inspiration for your
|
8
|
+
# own base `Edge` class.
|
9
|
+
#
|
10
|
+
# For example, you may want to extend your own `BaseObject` instead of the
|
11
|
+
# built-in `GraphQL::Schema::Object`.
|
12
|
+
#
|
13
|
+
# @example Making a UserEdge type
|
14
|
+
# # Make a base class for your app
|
15
|
+
# class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Then extend your own base class
|
19
|
+
# class Types::UserEdge < Types::BaseEdge
|
20
|
+
# node_type(Types::User)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @see {Relay::BaseConnection} for connection types
|
24
|
+
class BaseEdge < Types::Relay::BaseObject
|
25
|
+
description "An edge in a connection."
|
26
|
+
|
27
|
+
class << self
|
28
|
+
# Get or set the Object type that this edge wraps.
|
29
|
+
#
|
30
|
+
# @param node_type [Class] A `Schema::Object` subclass
|
31
|
+
def node_type(node_type = nil)
|
32
|
+
if node_type
|
33
|
+
@node_type = node_type
|
34
|
+
wrapped_type_name = node_type.graphql_name
|
35
|
+
# Set this to be named like the node type, but suffixed with `Edge`
|
36
|
+
graphql_name("#{wrapped_type_name}Edge")
|
37
|
+
# Add a default `node` field
|
38
|
+
field :node, node_type, null: true, description: "The item at the end of the edge."
|
39
|
+
end
|
40
|
+
@node_type
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
field :cursor, String,
|
46
|
+
null: false,
|
47
|
+
description: "A cursor for use in pagination."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
class BaseField < GraphQL::Schema::Field
|
7
|
+
def initialize(edge_class: nil, **rest, &block)
|
8
|
+
@edge_class = edge_class
|
9
|
+
super(**rest, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_graphql
|
13
|
+
field = super
|
14
|
+
if @edge_class
|
15
|
+
field.edge_class = @edge_class
|
16
|
+
end
|
17
|
+
field
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
module BaseInterface
|
7
|
+
include GraphQL::Schema::Interface
|
8
|
+
|
9
|
+
field_class(Types::Relay::BaseField)
|
10
|
+
|
11
|
+
definition_methods do
|
12
|
+
def default_relay(new_value)
|
13
|
+
@default_relay = new_value
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_relay?
|
17
|
+
!!@default_relay
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_graphql
|
21
|
+
type_defn = super
|
22
|
+
type_defn.default_relay = default_relay?
|
23
|
+
type_defn
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
class BaseObject < GraphQL::Schema::Object
|
7
|
+
field_class(Types::Relay::BaseField)
|
8
|
+
class << self
|
9
|
+
def default_relay(new_value)
|
10
|
+
@default_relay = new_value
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_relay?
|
14
|
+
!!@default_relay
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_graphql
|
18
|
+
type_defn = super
|
19
|
+
type_defn.default_relay = default_relay?
|
20
|
+
type_defn
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Types
|
5
|
+
module Relay
|
6
|
+
# This can be used for Relay's `Node` interface,
|
7
|
+
# or you can take it as inspiration for your own implementation
|
8
|
+
# of the `Node` interface.
|
9
|
+
module Node
|
10
|
+
include Types::Relay::BaseInterface
|
11
|
+
default_relay(true)
|
12
|
+
description "An object with an ID."
|
13
|
+
field(:id, ID, null: false, description: "ID of the object.")
|
14
|
+
# TODO Should I implement `id` here to call the schema's hook?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Types
|
4
|
+
module Relay
|
5
|
+
# The return type of a connection's `pageInfo` field
|
6
|
+
class PageInfo < Types::Relay::BaseObject
|
7
|
+
default_relay true
|
8
|
+
description "Information about pagination in a connection."
|
9
|
+
field :has_next_page, Boolean, null: false,
|
10
|
+
description: "When paginating forwards, are there more items?"
|
11
|
+
|
12
|
+
field :has_previous_page, Boolean, null: false,
|
13
|
+
description: "When paginating backwards, are there more items?"
|
14
|
+
|
15
|
+
field :start_cursor, String, null: true,
|
16
|
+
description: "When paginating backwards, the cursor to continue."
|
17
|
+
|
18
|
+
field :end_cursor, String, null: true,
|
19
|
+
description: "When paginating forwards, the cursor to continue."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|