graphql 0.17.2 → 0.18.0
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 +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
|
|