graphql 0.17.2 → 0.18.0
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 +1 -0
- data/lib/graphql/analysis/query_depth.rb +1 -1
- data/lib/graphql/base_type.rb +25 -1
- data/lib/graphql/define.rb +2 -0
- data/lib/graphql/define/assign_connection.rb +11 -0
- data/lib/graphql/define/assign_global_id_field.rb +11 -0
- data/lib/graphql/define/assign_object_field.rb +21 -20
- data/lib/graphql/define/defined_object_proxy.rb +2 -2
- data/lib/graphql/define/instance_definable.rb +13 -3
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/language/generation.rb +57 -6
- data/lib/graphql/language/lexer.rb +434 -212
- data/lib/graphql/language/lexer.rl +18 -0
- data/lib/graphql/language/nodes.rb +75 -0
- data/lib/graphql/language/parser.rb +853 -341
- data/lib/graphql/language/parser.y +114 -17
- data/lib/graphql/query.rb +15 -1
- data/lib/graphql/relay.rb +13 -0
- data/lib/graphql/relay/array_connection.rb +80 -0
- data/lib/graphql/relay/base_connection.rb +138 -0
- data/lib/graphql/relay/connection_field.rb +54 -0
- data/lib/graphql/relay/connection_type.rb +25 -0
- data/lib/graphql/relay/edge.rb +22 -0
- data/lib/graphql/relay/edge_type.rb +14 -0
- data/lib/graphql/relay/global_id_resolve.rb +15 -0
- data/lib/graphql/relay/global_node_identification.rb +124 -0
- data/lib/graphql/relay/mutation.rb +146 -0
- data/lib/graphql/relay/page_info.rb +13 -0
- data/lib/graphql/relay/relation_connection.rb +98 -0
- data/lib/graphql/schema.rb +3 -0
- data/lib/graphql/schema/printer.rb +12 -2
- data/lib/graphql/static_validation/message.rb +9 -5
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
- data/lib/graphql/static_validation/type_stack.rb +33 -2
- data/lib/graphql/static_validation/validation_context.rb +5 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +16 -4
- data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
- data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
- data/spec/graphql/argument_spec.rb +1 -1
- data/spec/graphql/define/instance_definable_spec.rb +9 -0
- data/spec/graphql/field_spec.rb +1 -1
- data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
- data/spec/graphql/language/generation_spec.rb +25 -4
- data/spec/graphql/language/parser_spec.rb +116 -1
- data/spec/graphql/query_spec.rb +10 -0
- data/spec/graphql/relay/array_connection_spec.rb +164 -0
- data/spec/graphql/relay/connection_type_spec.rb +37 -0
- data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
- data/spec/graphql/relay/mutation_spec.rb +55 -0
- data/spec/graphql/relay/page_info_spec.rb +106 -0
- data/spec/graphql/relay/relation_connection_spec.rb +348 -0
- data/spec/graphql/schema/printer_spec.rb +8 -0
- data/spec/graphql/schema/reduce_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
- data/spec/spec_helper.rb +7 -0
- data/spec/support/dairy_app.rb +11 -10
- data/spec/support/star_wars_data.rb +65 -58
- data/spec/support/star_wars_schema.rb +192 -54
- metadata +84 -2
data/lib/graphql/schema.rb
CHANGED
@@ -26,6 +26,9 @@ module GraphQL
|
|
26
26
|
:mutation_execution_strategy,
|
27
27
|
:subscription_execution_strategy
|
28
28
|
|
29
|
+
# @return [GraphQL::Relay::GlobalNodeIdentification] the node identification instance for this schema, when using Relay
|
30
|
+
attr_accessor :node_identification
|
31
|
+
|
29
32
|
# @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution
|
30
33
|
attr_reader :middleware
|
31
34
|
|
@@ -28,7 +28,17 @@ module GraphQL
|
|
28
28
|
|
29
29
|
def print_filtered_schema(schema, type_filter)
|
30
30
|
types = schema.types.values.select{ |type| type_filter.call(type) }.sort_by(&:name)
|
31
|
-
types.map{ |type| print_type(type) }
|
31
|
+
type_definitions = types.map{ |type| print_type(type) }
|
32
|
+
|
33
|
+
[print_schema_definition(schema)].concat(type_definitions).join("\n\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
def print_schema_definition(schema)
|
37
|
+
operations = [:query, :mutation, :subscription].map do |operation_type|
|
38
|
+
object_type = schema.public_send(operation_type)
|
39
|
+
" #{operation_type}: #{object_type.name}\n" if object_type
|
40
|
+
end.compact.join
|
41
|
+
"schema {\n#{operations}}"
|
32
42
|
end
|
33
43
|
|
34
44
|
BUILTIN_SCALARS = Set.new(["String", "Boolean", "Int", "Float", "ID"])
|
@@ -122,7 +132,7 @@ module GraphQL
|
|
122
132
|
|
123
133
|
class UnionPrinter
|
124
134
|
def self.print(type)
|
125
|
-
"union #{type.name} = #{type.possible_types.map(&:to_s).join(" | ")}
|
135
|
+
"union #{type.name} = #{type.possible_types.map(&:to_s).join(" | ")}"
|
126
136
|
end
|
127
137
|
end
|
128
138
|
|
@@ -6,23 +6,27 @@ module GraphQL
|
|
6
6
|
# Convenience for validators
|
7
7
|
module MessageHelper
|
8
8
|
# Error `message` is located at `node`
|
9
|
-
def message(message, node)
|
10
|
-
|
9
|
+
def message(message, node, context: nil, path: nil)
|
10
|
+
path ||= context.path
|
11
|
+
GraphQL::StaticValidation::Message.new(message, line: node.line, col: node.col, path: path)
|
11
12
|
end
|
12
13
|
end
|
13
|
-
attr_reader :message, :line, :col
|
14
14
|
|
15
|
-
|
15
|
+
attr_reader :message, :line, :col, :path
|
16
|
+
|
17
|
+
def initialize(message, line: nil, col: nil, path: [])
|
16
18
|
@message = message
|
17
19
|
@line = line
|
18
20
|
@col = col
|
21
|
+
@path = path
|
19
22
|
end
|
20
23
|
|
21
24
|
# A hash representation of this Message
|
22
25
|
def to_h
|
23
26
|
{
|
24
27
|
"message" => message,
|
25
|
-
"locations" => locations
|
28
|
+
"locations" => locations,
|
29
|
+
"path" => path,
|
26
30
|
}
|
27
31
|
end
|
28
32
|
|
@@ -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)
|
13
|
+
context.errors << message("Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value", parent, context: context)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -6,7 +6,7 @@ module GraphQL
|
|
6
6
|
if argument_defn.nil?
|
7
7
|
kind_of_node = node_type(parent)
|
8
8
|
error_arg_name = parent_name(parent, defn)
|
9
|
-
context.errors << message("#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", parent)
|
9
|
+
context.errors << message("#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", parent, context: context)
|
10
10
|
GraphQL::Language::Visitor::SKIP
|
11
11
|
else
|
12
12
|
nil
|
@@ -6,15 +6,15 @@ module GraphQL
|
|
6
6
|
def validate(context)
|
7
7
|
directive_names = context.schema.directives.keys
|
8
8
|
context.visitor[GraphQL::Language::Nodes::Directive] << -> (node, parent) {
|
9
|
-
validate_directive(node, directive_names, context
|
9
|
+
validate_directive(node, directive_names, context)
|
10
10
|
}
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
-
def validate_directive(ast_directive, directive_names,
|
15
|
+
def validate_directive(ast_directive, directive_names, context)
|
16
16
|
if !directive_names.include?(ast_directive.name)
|
17
|
-
errors << message("Directive @#{ast_directive.name} is not defined", ast_directive)
|
17
|
+
context.errors << message("Directive @#{ast_directive.name} is not defined", ast_directive, context: context)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
directives = context.schema.directives
|
9
9
|
|
10
10
|
context.visitor[Nodes::Directive] << -> (node, parent) {
|
11
|
-
validate_location(node, parent, directives, context
|
11
|
+
validate_location(node, parent, directives, context)
|
12
12
|
}
|
13
13
|
end
|
14
14
|
|
@@ -33,25 +33,25 @@ module GraphQL
|
|
33
33
|
|
34
34
|
SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys
|
35
35
|
|
36
|
-
def validate_location(ast_directive, ast_parent, directives,
|
36
|
+
def validate_location(ast_directive, ast_parent, directives, context)
|
37
37
|
directive_defn = directives[ast_directive.name]
|
38
38
|
case ast_parent
|
39
39
|
when Nodes::OperationDefinition
|
40
40
|
required_location = GraphQL::Directive.const_get(ast_parent.operation_type.upcase)
|
41
|
-
assert_includes_location(directive_defn, ast_directive, required_location,
|
41
|
+
assert_includes_location(directive_defn, ast_directive, required_location, context)
|
42
42
|
when *SIMPLE_LOCATION_NODES
|
43
43
|
required_location = SIMPLE_LOCATIONS[ast_parent.class]
|
44
|
-
assert_includes_location(directive_defn, ast_directive, required_location,
|
44
|
+
assert_includes_location(directive_defn, ast_directive, required_location, context)
|
45
45
|
else
|
46
|
-
errors << message("Directives can't be applied to #{ast_parent.class.name}s", ast_directive)
|
46
|
+
context.errors << message("Directives can't be applied to #{ast_parent.class.name}s", ast_directive, context: context)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
def assert_includes_location(directive_defn, directive_ast, required_location,
|
50
|
+
def assert_includes_location(directive_defn, directive_ast, required_location, context)
|
51
51
|
if !directive_defn.locations.include?(required_location)
|
52
52
|
location_name = LOCATION_MESSAGE_NAMES[required_location]
|
53
53
|
allowed_location_names = directive_defn.locations.map { |loc| LOCATION_MESSAGE_NAMES[loc] }
|
54
|
-
errors << message("'@#{directive_defn.name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", directive_ast)
|
54
|
+
context.errors << message("'@#{directive_defn.name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", directive_ast, context: context)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -9,21 +9,21 @@ module GraphQL
|
|
9
9
|
return if context.skip_field?(node.name)
|
10
10
|
parent_type = context.object_types[-2]
|
11
11
|
parent_type = parent_type.unwrap
|
12
|
-
validate_field(context
|
12
|
+
validate_field(context, node, parent_type, parent)
|
13
13
|
}
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
-
def validate_field(
|
18
|
+
def validate_field(context, ast_field, parent_type, parent)
|
19
19
|
if parent_type.kind.union?
|
20
|
-
errors << message("Selections can't be made directly on unions (see selections on #{parent_type.name})", parent)
|
20
|
+
context.errors << message("Selections can't be made directly on unions (see selections on #{parent_type.name})", parent, context: context)
|
21
21
|
return GraphQL::Language::Visitor::SKIP
|
22
22
|
end
|
23
23
|
|
24
24
|
field = parent_type.get_field(ast_field.name)
|
25
25
|
if field.nil?
|
26
|
-
errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent)
|
26
|
+
context.errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent, context: context)
|
27
27
|
return GraphQL::Language::Visitor::SKIP
|
28
28
|
end
|
29
29
|
end
|
@@ -9,25 +9,25 @@ module GraphQL
|
|
9
9
|
context.visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) {
|
10
10
|
return if context.skip_field?(node.name)
|
11
11
|
field_defn = context.field_definition
|
12
|
-
validate_field_selections(node, field_defn, context
|
12
|
+
validate_field_selections(node, field_defn, context)
|
13
13
|
}
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
-
def validate_field_selections(ast_field, field_defn,
|
18
|
+
def validate_field_selections(ast_field, field_defn, context)
|
19
19
|
resolved_type = field_defn.type.unwrap
|
20
20
|
|
21
21
|
if resolved_type.kind.scalar? && ast_field.selections.any?
|
22
|
-
error = message("Selections can't be made on scalars (field '#{ast_field.name}' returns #{resolved_type.name} but has selections [#{ast_field.selections.map(&:name).join(", ")}])", ast_field)
|
22
|
+
error = message("Selections can't be made on scalars (field '#{ast_field.name}' returns #{resolved_type.name} but has selections [#{ast_field.selections.map(&:name).join(", ")}])", ast_field, context: context)
|
23
23
|
elsif resolved_type.kind.object? && ast_field.selections.none?
|
24
|
-
error = message("Objects must have selections (field '#{ast_field.name}' returns #{resolved_type.name} but has no selections)", ast_field)
|
24
|
+
error = message("Objects must have selections (field '#{ast_field.name}' returns #{resolved_type.name} but has no selections)", ast_field, context: context)
|
25
25
|
else
|
26
26
|
error = nil
|
27
27
|
end
|
28
28
|
|
29
29
|
if !error.nil?
|
30
|
-
errors << error
|
30
|
+
context.errors << error
|
31
31
|
GraphQL::Language::Visitor::SKIP
|
32
32
|
end
|
33
33
|
end
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
22
22
|
|
23
23
|
def find_conflicts(field_map, context)
|
24
24
|
field_map.each do |name, ast_fields|
|
25
|
-
comparison = FieldDefinitionComparison.new(name, ast_fields)
|
25
|
+
comparison = FieldDefinitionComparison.new(name, ast_fields, context)
|
26
26
|
context.errors.push(*comparison.errors)
|
27
27
|
|
28
28
|
|
@@ -63,27 +63,27 @@ module GraphQL
|
|
63
63
|
include GraphQL::StaticValidation::Message::MessageHelper
|
64
64
|
NAMED_VALUES = [GraphQL::Language::Nodes::Enum, GraphQL::Language::Nodes::VariableIdentifier]
|
65
65
|
attr_reader :errors
|
66
|
-
def initialize(name, defs)
|
66
|
+
def initialize(name, defs, context)
|
67
67
|
errors = []
|
68
68
|
|
69
69
|
names = defs.map(&:name).uniq
|
70
70
|
if names.length != 1
|
71
|
-
errors << message("Field '#{name}' has a field conflict: #{names.join(" or ")}?", defs.first)
|
71
|
+
errors << message("Field '#{name}' has a field conflict: #{names.join(" or ")}?", defs.first, context: context)
|
72
72
|
end
|
73
73
|
|
74
74
|
args = defs.map { |defn| reduce_list(defn.arguments)}.uniq
|
75
75
|
if args.length != 1
|
76
|
-
errors << message("Field '#{name}' has an argument conflict: #{args.map {|a| JSON.dump(a) }.join(" or ")}?", defs.first)
|
76
|
+
errors << message("Field '#{name}' has an argument conflict: #{args.map {|a| JSON.dump(a) }.join(" or ")}?", defs.first, context: context)
|
77
77
|
end
|
78
78
|
|
79
79
|
directive_names = defs.map { |defn| defn.directives.map(&:name) }.uniq
|
80
80
|
if directive_names.length != 1
|
81
|
-
errors << message("Field '#{name}' has a directive conflict: #{directive_names.map {|names| "[#{names.join(", ")}]"}.join(" or ")}?", defs.first)
|
81
|
+
errors << message("Field '#{name}' has a directive conflict: #{directive_names.map {|names| "[#{names.join(", ")}]"}.join(" or ")}?", defs.first, context: context)
|
82
82
|
end
|
83
83
|
|
84
84
|
directive_args = defs.map {|defn| defn.directives.map {|d| reduce_list(d.arguments) } }.uniq
|
85
85
|
if directive_args.length != 1
|
86
|
-
errors << message("Field '#{name}' has a directive argument conflict: #{directive_args.map {|args| JSON.dump(args)}.join(" or ")}?", defs.first)
|
86
|
+
errors << message("Field '#{name}' has a directive argument conflict: #{directive_args.map {|args| JSON.dump(args)}.join(" or ")}?", defs.first, context: context)
|
87
87
|
end
|
88
88
|
|
89
89
|
@errors = errors
|
@@ -9,7 +9,7 @@ module GraphQL
|
|
9
9
|
fragment_parent = context.object_types[-2]
|
10
10
|
fragment_child = context.object_types.last
|
11
11
|
if fragment_child
|
12
|
-
validate_fragment_in_scope(fragment_parent, fragment_child, node, context)
|
12
|
+
validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
|
13
13
|
end
|
14
14
|
}
|
15
15
|
|
@@ -17,26 +17,25 @@ module GraphQL
|
|
17
17
|
|
18
18
|
context.visitor[GraphQL::Language::Nodes::FragmentSpread] << -> (node, parent) {
|
19
19
|
fragment_parent = context.object_types.last
|
20
|
-
spreads_to_validate <<
|
20
|
+
spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
|
21
21
|
}
|
22
22
|
|
23
|
-
context.visitor[GraphQL::Language::Nodes::Document].leave << -> (
|
24
|
-
spreads_to_validate.each do |
|
25
|
-
|
26
|
-
fragment_child_name = context.fragments[node.name].type
|
23
|
+
context.visitor[GraphQL::Language::Nodes::Document].leave << -> (doc_node, parent) {
|
24
|
+
spreads_to_validate.each do |frag_spread|
|
25
|
+
fragment_child_name = context.fragments[frag_spread.node.name].type
|
27
26
|
fragment_child = context.schema.types[fragment_child_name]
|
28
|
-
validate_fragment_in_scope(
|
27
|
+
validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
|
29
28
|
end
|
30
29
|
}
|
31
30
|
end
|
32
31
|
|
33
32
|
private
|
34
33
|
|
35
|
-
def validate_fragment_in_scope(parent_type, child_type, node, context)
|
34
|
+
def validate_fragment_in_scope(parent_type, child_type, node, context, path)
|
36
35
|
intersecting_types = get_possible_types(parent_type, context.schema) & get_possible_types(child_type, context.schema)
|
37
36
|
if intersecting_types.none?
|
38
37
|
name = node.respond_to?(:name) ? " #{node.name}" : ""
|
39
|
-
context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node)
|
38
|
+
context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node, path: path)
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
@@ -51,6 +50,15 @@ module GraphQL
|
|
51
50
|
[]
|
52
51
|
end
|
53
52
|
end
|
53
|
+
|
54
|
+
class FragmentSpread
|
55
|
+
attr_reader :node, :parent_type, :path
|
56
|
+
def initialize(node:, parent_type:, path:)
|
57
|
+
@node = node
|
58
|
+
@parent_type = parent_type
|
59
|
+
@path = path
|
60
|
+
end
|
61
|
+
end
|
54
62
|
end
|
55
63
|
end
|
56
64
|
end
|
@@ -20,7 +20,7 @@ module GraphQL
|
|
20
20
|
return unless node.type
|
21
21
|
type = context.schema.types.fetch(node.type, nil)
|
22
22
|
if type.nil?
|
23
|
-
context.errors << message("No such type #{node.type}, so it can't be a fragment condition", node)
|
23
|
+
context.errors << message("No such type #{node.type}, so it can't be a fragment condition", node, context: context)
|
24
24
|
GraphQL::Language::Visitor::SKIP
|
25
25
|
end
|
26
26
|
end
|
@@ -6,7 +6,7 @@ module GraphQL
|
|
6
6
|
def validate(context)
|
7
7
|
context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << -> (node, parent) {
|
8
8
|
if has_nested_spread(node, [], context)
|
9
|
-
context.errors << message("Fragment #{node.name} contains an infinite loop", node)
|
9
|
+
context.errors << message("Fragment #{node.name} contains an infinite loop", node, context: context)
|
10
10
|
end
|
11
11
|
}
|
12
12
|
end
|
@@ -23,7 +23,7 @@ module GraphQL
|
|
23
23
|
return unless type_name
|
24
24
|
type_def = context.schema.types[type_name]
|
25
25
|
if type_def.nil? || !type_def.kind.composite?
|
26
|
-
context.errors << message("Invalid fragment on type #{type_name} (must be Union, Interface or Object)", node)
|
26
|
+
context.errors << message("Invalid fragment on type #{type_name} (must be Union, Interface or Object)", node, context: context)
|
27
27
|
GraphQL::Language::Visitor::SKIP
|
28
28
|
end
|
29
29
|
end
|
@@ -9,35 +9,46 @@ module GraphQL
|
|
9
9
|
defined_fragments = []
|
10
10
|
|
11
11
|
v[GraphQL::Language::Nodes::Document] << -> (node, parent) {
|
12
|
-
defined_fragments = node.definitions
|
12
|
+
defined_fragments = node.definitions
|
13
|
+
.select { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
|
14
|
+
.map { |node| FragmentInstance.new(node: node, path: context.path) }
|
13
15
|
}
|
14
16
|
|
15
17
|
v[GraphQL::Language::Nodes::FragmentSpread] << -> (node, parent) {
|
16
|
-
used_fragments << node
|
18
|
+
used_fragments << FragmentInstance.new(node: node, path: context.path)
|
17
19
|
if defined_fragments.none? { |defn| defn.name == node.name }
|
18
20
|
GraphQL::Language::Visitor::SKIP
|
19
21
|
end
|
20
22
|
}
|
21
|
-
v[GraphQL::Language::Nodes::Document].leave << -> (node, parent) { add_errors(context
|
23
|
+
v[GraphQL::Language::Nodes::Document].leave << -> (node, parent) { add_errors(context, used_fragments, defined_fragments) }
|
22
24
|
end
|
23
25
|
|
24
26
|
private
|
25
27
|
|
26
|
-
def add_errors(
|
28
|
+
def add_errors(context, used_fragments, defined_fragments)
|
27
29
|
undefined_fragments = find_difference(used_fragments, defined_fragments.map(&:name))
|
28
30
|
undefined_fragments.each do |fragment|
|
29
|
-
errors << message("Fragment #{fragment.name} was used, but not defined", fragment)
|
31
|
+
context.errors << message("Fragment #{fragment.name} was used, but not defined", fragment.node, path: fragment.path)
|
30
32
|
end
|
31
33
|
|
32
34
|
unused_fragments = find_difference(defined_fragments, used_fragments.map(&:name))
|
33
35
|
unused_fragments.each do |fragment|
|
34
|
-
errors << message("Fragment #{fragment.name} was defined, but not used", fragment)
|
36
|
+
context.errors << message("Fragment #{fragment.name} was defined, but not used", fragment.node, path: fragment.path)
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
40
|
def find_difference(fragments, allowed_fragment_names)
|
39
41
|
fragments.select {|f| !allowed_fragment_names.include?(f.name) }
|
40
42
|
end
|
43
|
+
|
44
|
+
class FragmentInstance
|
45
|
+
attr_reader :name, :node, :path
|
46
|
+
def initialize(node:, path:)
|
47
|
+
@node = node
|
48
|
+
@name = node.name
|
49
|
+
@path = path
|
50
|
+
end
|
51
|
+
end
|
41
52
|
end
|
42
53
|
end
|
43
54
|
end
|
@@ -30,7 +30,7 @@ module GraphQL
|
|
30
30
|
|
31
31
|
missing_names = required_argument_names - present_argument_names
|
32
32
|
if missing_names.any?
|
33
|
-
context.errors << message("#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", ast_node)
|
33
|
+
context.errors << message("#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", ast_node, context: context)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -15,11 +15,11 @@ module GraphQL
|
|
15
15
|
def validate_default_value(node, literal_validator, context)
|
16
16
|
value = node.default_value
|
17
17
|
if node.type.is_a?(GraphQL::Language::Nodes::NonNullType)
|
18
|
-
context.errors << message("Non-null variable $#{node.name} can't have a default value", node)
|
18
|
+
context.errors << message("Non-null variable $#{node.name} can't have a default value", node, context: context)
|
19
19
|
else
|
20
20
|
type = context.schema.type_from_ast(node.type)
|
21
21
|
if !literal_validator.validate(value, type)
|
22
|
-
context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node)
|
22
|
+
context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node, context: context)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -44,11 +44,11 @@ module GraphQL
|
|
44
44
|
arg_inner_type = arg_defn_type.unwrap
|
45
45
|
|
46
46
|
if var_inner_type != arg_inner_type
|
47
|
-
context.errors << create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node)
|
47
|
+
context.errors << create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node, context)
|
48
48
|
elsif list_dimension(var_type) != list_dimension(arg_defn_type)
|
49
|
-
context.errors << create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node)
|
49
|
+
context.errors << create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node, context)
|
50
50
|
elsif !non_null_levels_match(arg_defn_type, var_type)
|
51
|
-
context.errors << create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node)
|
51
|
+
context.errors << create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node, context)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
@@ -62,8 +62,8 @@ module GraphQL
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
def create_error(error_message, var_type, ast_var, arg_defn, arg_node)
|
66
|
-
message("#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})", arg_node)
|
65
|
+
def create_error(error_message, var_type, ast_var, arg_defn, arg_node, context)
|
66
|
+
message("#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})", arg_node, context: context)
|
67
67
|
end
|
68
68
|
|
69
69
|
def list_dimension(type)
|
@@ -15,7 +15,7 @@ module GraphQL
|
|
15
15
|
type_name = get_type_name(node.type)
|
16
16
|
type = context.schema.types[type_name]
|
17
17
|
if !type.kind.input?
|
18
|
-
context.errors << message("#{type.name} isn't a valid input type (on $#{node.name})", node)
|
18
|
+
context.errors << message("#{type.name} isn't a valid input type (on $#{node.name})", node, context: context)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|