graphql 1.8.0.pre4 → 1.8.0.pre5
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/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!
|