graphql 1.8.3 → 1.8.4
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.
- 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
|