graphql 1.8.0.pre4 → 1.8.0.pre5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/base_type.rb +10 -27
- data/lib/graphql/compatibility/query_parser_specification.rb +7 -0
- data/lib/graphql/field.rb +3 -3
- data/lib/graphql/internal_representation/node.rb +32 -13
- data/lib/graphql/internal_representation/visit.rb +3 -6
- data/lib/graphql/language.rb +1 -0
- data/lib/graphql/language/block_string.rb +47 -0
- data/lib/graphql/language/lexer.rb +129 -68
- data/lib/graphql/language/lexer.rl +13 -4
- data/lib/graphql/language/nodes.rb +6 -3
- data/lib/graphql/language/printer.rb +1 -1
- data/lib/graphql/language/token.rb +1 -1
- data/lib/graphql/query.rb +1 -1
- data/lib/graphql/relay.rb +1 -0
- data/lib/graphql/relay/connection_type.rb +5 -3
- data/lib/graphql/relay/edge_type.rb +2 -1
- data/lib/graphql/relay/type_extensions.rb +30 -0
- data/lib/graphql/schema.rb +25 -0
- data/lib/graphql/schema/build_from_definition.rb +2 -0
- data/lib/graphql/schema/field.rb +3 -0
- data/lib/graphql/schema/finder.rb +153 -0
- data/lib/graphql/schema/member.rb +3 -1
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +11 -1
- data/lib/graphql/tracing/data_dog_tracing.rb +13 -9
- data/lib/graphql/upgrader/member.rb +19 -8
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/backtrace_spec.rb +10 -0
- data/spec/graphql/directive_spec.rb +3 -1
- data/spec/graphql/language/block_string_spec.rb +70 -0
- data/spec/graphql/language/lexer_spec.rb +9 -0
- data/spec/graphql/query_spec.rb +1 -1
- data/spec/graphql/schema/field_spec.rb +8 -0
- data/spec/graphql/schema/finder_spec.rb +135 -0
- data/spec/graphql/schema/printer_spec.rb +48 -5
- data/spec/graphql/schema_spec.rb +7 -0
- data/spec/graphql/upgrader/member_spec.rb +25 -0
- metadata +23 -2
@@ -34,6 +34,9 @@
|
|
34
34
|
RBRACKET = ']';
|
35
35
|
COLON = ':';
|
36
36
|
QUOTE = '"';
|
37
|
+
BLOCK_QUOTE = '"""';
|
38
|
+
ESCAPED_BLOCK_QUOTE = '\\"""';
|
39
|
+
BLOCK_STRING_CHAR = (ESCAPED_BLOCK_QUOTE | ^'"""');
|
37
40
|
ESCAPED_QUOTE = '\\"';
|
38
41
|
STRING_CHAR = (ESCAPED_QUOTE | ^'"');
|
39
42
|
VAR_SIGN = '$';
|
@@ -44,7 +47,7 @@
|
|
44
47
|
PIPE = '|';
|
45
48
|
|
46
49
|
QUOTED_STRING = QUOTE STRING_CHAR* QUOTE;
|
47
|
-
|
50
|
+
BLOCK_STRING = BLOCK_QUOTE BLOCK_STRING_CHAR* BLOCK_QUOTE;
|
48
51
|
# catch-all for anything else. must be at the bottom for precedence.
|
49
52
|
UNKNOWN_CHAR = /./;
|
50
53
|
|
@@ -75,7 +78,8 @@
|
|
75
78
|
RBRACKET => { emit(:RBRACKET, ts, te, meta) };
|
76
79
|
LBRACKET => { emit(:LBRACKET, ts, te, meta) };
|
77
80
|
COLON => { emit(:COLON, ts, te, meta) };
|
78
|
-
QUOTED_STRING => { emit_string(ts
|
81
|
+
QUOTED_STRING => { emit_string(ts, te, meta, block: false) };
|
82
|
+
BLOCK_STRING => { emit_string(ts, te, meta, block: true) };
|
79
83
|
VAR_SIGN => { emit(:VAR_SIGN, ts, te, meta) };
|
80
84
|
DIR_SIGN => { emit(:DIR_SIGN, ts, te, meta) };
|
81
85
|
ELLIPSIS => { emit(:ELLIPSIS, ts, te, meta) };
|
@@ -188,8 +192,13 @@ module GraphQL
|
|
188
192
|
PACK_DIRECTIVE = "c*"
|
189
193
|
UTF_8_ENCODING = "UTF-8"
|
190
194
|
|
191
|
-
def self.emit_string(ts, te, meta)
|
192
|
-
|
195
|
+
def self.emit_string(ts, te, meta, block:)
|
196
|
+
quotes_length = block ? 3 : 1
|
197
|
+
ts += quotes_length
|
198
|
+
value = meta[:data][ts...te - quotes_length].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING)
|
199
|
+
if block
|
200
|
+
value = GraphQL::Language::BlockString.trim_whitespace(value)
|
201
|
+
end
|
193
202
|
if value !~ VALID_STRING
|
194
203
|
meta[:tokens] << token = GraphQL::Language::Token.new(
|
195
204
|
name: :BAD_UNICODE_ESCAPE,
|
@@ -288,13 +288,16 @@ module GraphQL
|
|
288
288
|
private
|
289
289
|
|
290
290
|
def serialize_value_for_hash(value)
|
291
|
-
|
291
|
+
case value
|
292
|
+
when InputObject
|
292
293
|
value.to_h
|
293
|
-
|
294
|
+
when Array
|
294
295
|
value.map do |v|
|
295
296
|
serialize_value_for_hash v
|
296
297
|
end
|
297
|
-
|
298
|
+
when Enum
|
299
|
+
value.name
|
300
|
+
when NullValue
|
298
301
|
nil
|
299
302
|
else
|
300
303
|
value
|
data/lib/graphql/query.rb
CHANGED
@@ -160,7 +160,7 @@ module GraphQL
|
|
160
160
|
def result
|
161
161
|
if !@executed
|
162
162
|
with_prepared_ast {
|
163
|
-
Execution::Multiplex.run_queries(@schema, [self])
|
163
|
+
Execution::Multiplex.run_queries(@schema, [self], context: @context)
|
164
164
|
}
|
165
165
|
end
|
166
166
|
@result ||= Query::Result.new(query: self, values: @result_values)
|
data/lib/graphql/relay.rb
CHANGED
@@ -13,15 +13,17 @@ module GraphQL
|
|
13
13
|
self.bidirectional_pagination = false
|
14
14
|
|
15
15
|
# Create a connection which exposes edges of this type
|
16
|
-
def self.create_type(wrapped_type, edge_type:
|
16
|
+
def self.create_type(wrapped_type, edge_type: nil, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block)
|
17
17
|
custom_edge_class = edge_class
|
18
18
|
|
19
19
|
# Any call that would trigger `wrapped_type.ensure_defined`
|
20
20
|
# must be inside this lazy block, otherwise we get weird
|
21
21
|
# cyclical dependency errors :S
|
22
22
|
ObjectType.define do
|
23
|
-
|
24
|
-
|
23
|
+
type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name
|
24
|
+
edge_type ||= wrapped_type.edge_type
|
25
|
+
name("#{type_name}Connection")
|
26
|
+
description("The connection type for #{type_name}.")
|
25
27
|
field :edges, types[edge_type], "A list of edges.", edge_class: custom_edge_class, property: :edge_nodes
|
26
28
|
|
27
29
|
if nodes_field
|
@@ -4,7 +4,8 @@ module GraphQL
|
|
4
4
|
module EdgeType
|
5
5
|
def self.create_type(wrapped_type, name: nil, &block)
|
6
6
|
GraphQL::ObjectType.define do
|
7
|
-
|
7
|
+
type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name
|
8
|
+
name("#{type_name}Edge")
|
8
9
|
description "An edge in a connection."
|
9
10
|
field :node, wrapped_type, "The item at the end of the edge."
|
10
11
|
field :cursor, !types.String, "A cursor for use in pagination."
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Relay
|
4
|
+
# Mixin for Relay-related methods in type objects
|
5
|
+
# (used by BaseType and Schema::Member).
|
6
|
+
module TypeExtensions
|
7
|
+
# @return [GraphQL::ObjectType] The default connection type for this object type
|
8
|
+
def connection_type
|
9
|
+
@connection_type ||= define_connection
|
10
|
+
end
|
11
|
+
|
12
|
+
# Define a custom connection type for this object type
|
13
|
+
# @return [GraphQL::ObjectType]
|
14
|
+
def define_connection(**kwargs, &block)
|
15
|
+
GraphQL::Relay::ConnectionType.create_type(self, **kwargs, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [GraphQL::ObjectType] The default edge type for this object type
|
19
|
+
def edge_type
|
20
|
+
@edge_type ||= define_edge
|
21
|
+
end
|
22
|
+
|
23
|
+
# Define a custom edge type for this object type
|
24
|
+
# @return [GraphQL::ObjectType]
|
25
|
+
def define_edge(**kwargs, &block)
|
26
|
+
GraphQL::Relay::EdgeType.create_type(self, **kwargs, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/graphql/schema.rb
CHANGED
@@ -3,6 +3,7 @@ require "graphql/schema/base_64_encoder"
|
|
3
3
|
require "graphql/schema/catchall_middleware"
|
4
4
|
require "graphql/schema/default_parse_error"
|
5
5
|
require "graphql/schema/default_type_error"
|
6
|
+
require "graphql/schema/finder"
|
6
7
|
require "graphql/schema/invalid_type_error"
|
7
8
|
require "graphql/schema/introspection_system"
|
8
9
|
require "graphql/schema/late_bound_type"
|
@@ -329,6 +330,19 @@ module GraphQL
|
|
329
330
|
}
|
330
331
|
end
|
331
332
|
|
333
|
+
# Search for a schema member using a string path
|
334
|
+
# @example Finding a Field
|
335
|
+
# Schema.find("Ensemble.musicians")
|
336
|
+
#
|
337
|
+
# @see {GraphQL::Schema::Finder} for more examples
|
338
|
+
# @param path [String] A dot-separated path to the member
|
339
|
+
# @raise [Schema::Finder::MemberNotFoundError] if path could not be found
|
340
|
+
# @return [GraphQL::BaseType, GraphQL::Field, GraphQL::Argument, GraphQL::Directive] A GraphQL Schema Member
|
341
|
+
def find(path)
|
342
|
+
rebuild_artifacts unless defined?(@finder)
|
343
|
+
@find_cache[path] ||= @finder.find(path)
|
344
|
+
end
|
345
|
+
|
332
346
|
# Resolve field named `field_name` for type `parent_type`.
|
333
347
|
# Handles dynamic fields `__typename`, `__type` and `__schema`, too
|
334
348
|
# @param parent_type [String, GraphQL::BaseType]
|
@@ -641,6 +655,7 @@ module GraphQL
|
|
641
655
|
schema_defn.query = query
|
642
656
|
schema_defn.mutation = mutation
|
643
657
|
schema_defn.subscription = subscription
|
658
|
+
schema_defn.max_complexity = max_complexity
|
644
659
|
schema_defn.max_depth = max_depth
|
645
660
|
schema_defn.default_max_page_size = default_max_page_size
|
646
661
|
schema_defn.orphan_types = orphan_types
|
@@ -720,6 +735,14 @@ module GraphQL
|
|
720
735
|
end
|
721
736
|
end
|
722
737
|
|
738
|
+
def max_complexity(max_complexity = nil)
|
739
|
+
if max_complexity
|
740
|
+
@max_complexity = max_complexity
|
741
|
+
else
|
742
|
+
@max_complexity
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
723
746
|
def max_depth(new_max_depth = nil)
|
724
747
|
if new_max_depth
|
725
748
|
@max_depth = new_max_depth
|
@@ -846,6 +869,8 @@ module GraphQL
|
|
846
869
|
@instrumented_field_map = traversal.instrumented_field_map
|
847
870
|
@type_reference_map = traversal.type_reference_map
|
848
871
|
@union_memberships = traversal.union_memberships
|
872
|
+
@find_cache = {}
|
873
|
+
@finder = Finder.new(self)
|
849
874
|
end
|
850
875
|
ensure
|
851
876
|
@rebuilding_artifacts = false
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -16,6 +16,9 @@ module GraphQL
|
|
16
16
|
# @return [Hash{String => GraphQL::Schema::Argument}]
|
17
17
|
attr_reader :arguments
|
18
18
|
|
19
|
+
# @return [Symbol]
|
20
|
+
attr_reader :method
|
21
|
+
|
19
22
|
def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, extras: [], &definition_block)
|
20
23
|
if !(field || function)
|
21
24
|
if return_type_expr.nil?
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
# Find schema members using string paths
|
6
|
+
#
|
7
|
+
# @example Finding object types
|
8
|
+
# MySchema.find("SomeObjectType")
|
9
|
+
#
|
10
|
+
# @example Finding fields
|
11
|
+
# MySchema.find("SomeObjectType.myField")
|
12
|
+
#
|
13
|
+
# @example Finding arguments
|
14
|
+
# MySchema.find("SomeObjectType.myField.anArgument")
|
15
|
+
#
|
16
|
+
# @example Finding directives
|
17
|
+
# MySchema.find("@include")
|
18
|
+
#
|
19
|
+
class Finder
|
20
|
+
class MemberNotFoundError < ArgumentError; end
|
21
|
+
|
22
|
+
def initialize(schema)
|
23
|
+
@schema = schema
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(path)
|
27
|
+
path = path.split(".")
|
28
|
+
type_or_directive = path.shift
|
29
|
+
|
30
|
+
if type_or_directive.start_with?("@")
|
31
|
+
directive = schema.directives[type_or_directive[1..-1]]
|
32
|
+
|
33
|
+
if directive.nil?
|
34
|
+
raise MemberNotFoundError, "Could not find directive `#{type_or_directive}` in schema."
|
35
|
+
end
|
36
|
+
|
37
|
+
return directive if path.empty?
|
38
|
+
|
39
|
+
find_in_directive(directive, path: path)
|
40
|
+
else
|
41
|
+
type = schema.types[type_or_directive]
|
42
|
+
|
43
|
+
if type.nil?
|
44
|
+
raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema."
|
45
|
+
end
|
46
|
+
|
47
|
+
return type if path.empty?
|
48
|
+
|
49
|
+
find_in_type(type, path: path)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :schema
|
56
|
+
|
57
|
+
def find_in_directive(directive, path:)
|
58
|
+
argument_name = path.shift
|
59
|
+
argument = directive.arguments[argument_name]
|
60
|
+
|
61
|
+
if argument.nil?
|
62
|
+
raise MemberNotFoundError, "Could not find argument `#{argument_name}` on directive #{directive}."
|
63
|
+
end
|
64
|
+
|
65
|
+
argument
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_in_type(type, path:)
|
69
|
+
case type
|
70
|
+
when GraphQL::ObjectType
|
71
|
+
find_in_fields_type(type, kind: "object", path: path)
|
72
|
+
when GraphQL::InterfaceType
|
73
|
+
find_in_fields_type(type, kind: "interface", path: path)
|
74
|
+
when GraphQL::InputObjectType
|
75
|
+
find_in_input_object(type, path: path)
|
76
|
+
when GraphQL::UnionType
|
77
|
+
# Error out if path that was provided is too long
|
78
|
+
# i.e UnionType.PossibleType.aField
|
79
|
+
# Use PossibleType.aField instead.
|
80
|
+
if invalid = path.first
|
81
|
+
raise MemberNotFoundError, "Cannot select union possible type `#{invalid}`. Select the type directly instead."
|
82
|
+
end
|
83
|
+
when GraphQL::EnumType
|
84
|
+
find_in_enum_type(type, path: path)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def find_in_fields_type(type, kind:, path:)
|
89
|
+
field_name = path.shift
|
90
|
+
field = schema.get_field(type, field_name)
|
91
|
+
|
92
|
+
if field.nil?
|
93
|
+
raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type}`."
|
94
|
+
end
|
95
|
+
|
96
|
+
return field if path.empty?
|
97
|
+
|
98
|
+
find_in_field(field, path: path)
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_in_field(field, path:)
|
102
|
+
argument_name = path.shift
|
103
|
+
argument = field.arguments[argument_name]
|
104
|
+
|
105
|
+
if argument.nil?
|
106
|
+
raise MemberNotFoundError, "Could not find argument `#{argument_name}` on field `#{field.name}`."
|
107
|
+
end
|
108
|
+
|
109
|
+
# Error out if path that was provided is too long
|
110
|
+
# i.e Type.field.argument.somethingBad
|
111
|
+
if invalid = path.first
|
112
|
+
raise MemberNotFoundError, "Cannot select member `#{invalid}` on a field."
|
113
|
+
end
|
114
|
+
|
115
|
+
argument
|
116
|
+
end
|
117
|
+
|
118
|
+
def find_in_input_object(input_object, path:)
|
119
|
+
field_name = path.shift
|
120
|
+
input_field = input_object.input_fields[field_name]
|
121
|
+
|
122
|
+
if input_field.nil?
|
123
|
+
raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object}`."
|
124
|
+
end
|
125
|
+
|
126
|
+
# Error out if path that was provided is too long
|
127
|
+
# i.e InputType.inputField.bad
|
128
|
+
if invalid = path.first
|
129
|
+
raise MemberNotFoundError, "Cannot select member `#{invalid}` on an input field."
|
130
|
+
end
|
131
|
+
|
132
|
+
input_field
|
133
|
+
end
|
134
|
+
|
135
|
+
def find_in_enum_type(enum_type, path:)
|
136
|
+
value_name = path.shift
|
137
|
+
enum_value = enum_type.values[value_name]
|
138
|
+
|
139
|
+
if enum_value.nil?
|
140
|
+
raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type}`."
|
141
|
+
end
|
142
|
+
|
143
|
+
# Error out if path that was provided is too long
|
144
|
+
# i.e Enum.VALUE.wat
|
145
|
+
if invalid = path.first
|
146
|
+
raise MemberNotFoundError, "Cannot select member `#{invalid}` on an enum value."
|
147
|
+
end
|
148
|
+
|
149
|
+
enum_value
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "graphql/relay/type_extensions"
|
3
|
+
|
2
4
|
module GraphQL
|
3
5
|
# The base class for things that make up the schema,
|
4
6
|
# eg objects, enums, scalars.
|
@@ -35,7 +37,7 @@ module GraphQL
|
|
35
37
|
include GraphQLTypeNames
|
36
38
|
class << self
|
37
39
|
include CachedGraphQLDefinition
|
38
|
-
|
40
|
+
include GraphQL::Relay::TypeExtensions
|
39
41
|
# Delegate to the derived type definition if possible.
|
40
42
|
# This is tricky because missing methods cause the definition to be built & cached.
|
41
43
|
def method_missing(method_name, *args, &block)
|
@@ -2,6 +2,9 @@
|
|
2
2
|
module GraphQL
|
3
3
|
module StaticValidation
|
4
4
|
class FieldsWillMerge
|
5
|
+
# Special handling for fields without arguments
|
6
|
+
NO_ARGS = {}.freeze
|
7
|
+
|
5
8
|
def validate(context)
|
6
9
|
context.each_irep_node do |node|
|
7
10
|
if node.ast_nodes.size > 1
|
@@ -16,15 +19,19 @@ module GraphQL
|
|
16
19
|
|
17
20
|
# Check for incompatible / non-identical arguments on this node:
|
18
21
|
args = node.ast_nodes.map do |n|
|
19
|
-
n.arguments.
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
if n.arguments.any?
|
23
|
+
n.arguments.reduce({}) do |memo, a|
|
24
|
+
arg_value = a.value
|
25
|
+
memo[a.name] = case arg_value
|
26
|
+
when GraphQL::Language::Nodes::AbstractNode
|
27
|
+
arg_value.to_query_string
|
28
|
+
else
|
29
|
+
GraphQL::Language.serialize(arg_value)
|
30
|
+
end
|
31
|
+
memo
|
26
32
|
end
|
27
|
-
|
33
|
+
else
|
34
|
+
NO_ARGS
|
28
35
|
end
|
29
36
|
end
|
30
37
|
args.uniq!
|