graphql 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graph_ql/{types → definition_helpers}/argument_definer.rb +0 -0
- data/lib/graph_ql/definition_helpers/definable.rb +18 -0
- data/lib/graph_ql/{types → definition_helpers}/field_definer.rb +0 -0
- data/lib/graph_ql/definition_helpers/forwardable.rb +10 -0
- data/lib/graph_ql/definition_helpers/non_null_with_bang.rb +9 -0
- data/lib/graph_ql/definition_helpers/string_named_hash.rb +12 -0
- data/lib/graph_ql/{types → definition_helpers}/type_definer.rb +1 -0
- data/lib/graph_ql/directive.rb +9 -5
- data/lib/graph_ql/directives/directive_chain.rb +1 -1
- data/lib/graph_ql/field.rb +1 -5
- data/lib/graph_ql/parser/transform.rb +1 -3
- data/lib/graph_ql/query.rb +14 -3
- data/lib/graph_ql/query/arguments.rb +6 -5
- data/lib/graph_ql/query/field_resolution_strategy.rb +3 -3
- data/lib/graph_ql/{types → scalars}/boolean_type.rb +0 -0
- data/lib/graph_ql/{types → scalars}/float_type.rb +1 -1
- data/lib/graph_ql/scalars/id_type.rb +6 -0
- data/lib/graph_ql/{types → scalars}/int_type.rb +0 -0
- data/lib/graph_ql/{types → scalars}/scalar_type.rb +0 -4
- data/lib/graph_ql/{types → scalars}/string_type.rb +0 -0
- data/lib/graph_ql/static_validation.rb +7 -10
- data/lib/graph_ql/static_validation/all_rules.rb +23 -0
- data/lib/graph_ql/static_validation/literal_validator.rb +0 -3
- data/lib/graph_ql/static_validation/{argument_literals_are_compatible.rb → rules/argument_literals_are_compatible.rb} +0 -0
- data/lib/graph_ql/static_validation/{arguments_are_defined.rb → rules/arguments_are_defined.rb} +5 -0
- data/lib/graph_ql/static_validation/{directives_are_defined.rb → rules/directives_are_defined.rb} +0 -0
- data/lib/graph_ql/static_validation/{fields_are_defined_on_type.rb → rules/fields_are_defined_on_type.rb} +0 -0
- data/lib/graph_ql/static_validation/{fields_have_appropriate_selections.rb → rules/fields_have_appropriate_selections.rb} +0 -0
- data/lib/graph_ql/static_validation/{fields_will_merge.rb → rules/fields_will_merge.rb} +13 -5
- data/lib/graph_ql/static_validation/rules/fragment_spreads_are_possible.rb +50 -0
- data/lib/graph_ql/static_validation/{fragment_types_exist.rb → rules/fragment_types_exist.rb} +0 -0
- data/lib/graph_ql/static_validation/rules/fragments_are_finite.rb +23 -0
- data/lib/graph_ql/static_validation/rules/fragments_are_on_composite_types.rb +27 -0
- data/lib/graph_ql/static_validation/{fragments_are_used.rb → rules/fragments_are_used.rb} +0 -0
- data/lib/graph_ql/static_validation/{required_arguments_are_present.rb → rules/required_arguments_are_present.rb} +0 -0
- data/lib/graph_ql/static_validation/rules/variable_default_values_are_correctly_typed.rb +24 -0
- data/lib/graph_ql/static_validation/rules/variable_usages_are_allowed.rb +47 -0
- data/lib/graph_ql/static_validation/rules/variables_are_input_types.rb +27 -0
- data/lib/graph_ql/static_validation/rules/variables_are_used_and_defined.rb +31 -0
- data/lib/graph_ql/static_validation/type_stack.rb +13 -1
- data/lib/graph_ql/static_validation/validator.rb +14 -18
- data/lib/graph_ql/type_kinds.rb +8 -4
- data/lib/graph_ql/{enum.rb → types/enum.rb} +7 -6
- data/lib/graph_ql/types/input_object_type.rb +3 -10
- data/lib/graph_ql/{interface.rb → types/interface.rb} +0 -4
- data/lib/graph_ql/types/list_type.rb +0 -4
- data/lib/graph_ql/types/non_null_type.rb +0 -4
- data/lib/graph_ql/types/object_type.rb +28 -13
- data/lib/graph_ql/{union.rb → types/union.rb} +0 -4
- data/lib/graph_ql/version.rb +1 -1
- data/lib/graphql.rb +9 -40
- data/readme.md +26 -20
- data/spec/graph_ql/directive_spec.rb +4 -4
- data/spec/graph_ql/introspection/directive_type_spec.rb +2 -2
- data/spec/graph_ql/introspection/schema_type_spec.rb +3 -2
- data/spec/graph_ql/introspection/type_type_spec.rb +7 -7
- data/spec/graph_ql/parser/parser_spec.rb +6 -2
- data/spec/graph_ql/query_spec.rb +26 -8
- data/spec/graph_ql/scalars/id_type_spec.rb +24 -0
- data/spec/graph_ql/schema/type_reducer_spec.rb +1 -0
- data/spec/graph_ql/schema/type_validator_spec.rb +1 -1
- data/spec/graph_ql/static_validation/{argument_literals_are_compatible_spec.rb → rules/argument_literals_are_compatible_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/{arguments_are_defined_spec.rb → rules/arguments_are_defined_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/{directives_are_defined_spec.rb → rules/directives_are_defined_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/{fields_are_defined_on_type_spec.rb → rules/fields_are_defined_on_type_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/{fields_have_appropriate_selections_spec.rb → rules/fields_have_appropriate_selections_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/{fields_will_merge_spec.rb → rules/fields_will_merge_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/rules/fragment_spreads_are_possible_spec.rb +46 -0
- data/spec/graph_ql/static_validation/{fragment_types_exist_spec.rb → rules/fragment_types_exist_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/rules/fragments_are_finite_spec.rb +43 -0
- data/spec/graph_ql/static_validation/rules/fragments_are_on_composite_types_spec.rb +48 -0
- data/spec/graph_ql/static_validation/{fragments_are_used_spec.rb → rules/fragments_are_used_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/{required_arguments_are_present_spec.rb → rules/required_arguments_are_present_spec.rb} +1 -1
- data/spec/graph_ql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +43 -0
- data/spec/graph_ql/static_validation/rules/variable_usages_are_allowed_spec.rb +45 -0
- data/spec/graph_ql/static_validation/rules/variables_are_input_types_spec.rb +36 -0
- data/spec/graph_ql/static_validation/rules/variables_are_used_and_defined_spec.rb +44 -0
- data/spec/graph_ql/static_validation/type_stack_spec.rb +2 -2
- data/spec/graph_ql/static_validation/validator_spec.rb +44 -5
- data/spec/graph_ql/types/enum_spec.rb +10 -0
- data/spec/graph_ql/{interface_spec.rb → types/interface_spec.rb} +1 -1
- data/spec/graph_ql/types/object_type_spec.rb +13 -0
- data/spec/graph_ql/{union_spec.rb → types/union_spec.rb} +0 -0
- data/spec/support/dummy_app.rb +7 -6
- data/spec/support/dummy_data.rb +3 -3
- metadata +74 -46
- data/lib/graph_ql/types/non_null_with_bang.rb +0 -5
- data/spec/graph_ql/enum_spec.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cf6add0a41fbbf194c90bd12da917467ae612d7
|
4
|
+
data.tar.gz: b0e7b7f03eb0e6a9def2f7402f3349e69d07ec33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5d0f097d277cdd8e40976ce87ef5d57edf365c2fadf821de5c90d7dad9f9d7819a21291c6b01a83ab05e1d7850617bbbe0382c492776b04e5fa619e186e8caa
|
7
|
+
data.tar.gz: ee252ea72993ee9c07ce9d69f6f25c0b6d13e9f7f2bf7cbbb419d8d2b01f48a9c7e57310c6cc05e051c833ee72b42494399bd5252813a4641d1ba3e88be17d16
|
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Define attributes which can be assigned & read, or
|
2
|
+
# "defined", by passing the new value as an argument
|
3
|
+
#
|
4
|
+
# @example defining an object's name
|
5
|
+
# object.name("New name")
|
6
|
+
#
|
7
|
+
module GraphQL::Definable
|
8
|
+
def attr_definable(*names)
|
9
|
+
attr_accessor(*names)
|
10
|
+
names.each do |name|
|
11
|
+
ivar_name = "@#{name}".to_sym
|
12
|
+
define_method(name) do |new_value=nil|
|
13
|
+
new_value && self.instance_variable_set(ivar_name, new_value)
|
14
|
+
instance_variable_get(ivar_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Accepts a hash with symbol keys.
|
2
|
+
# - convert keys to strings
|
3
|
+
# - if the value responds to `name=`, then assign the hash key as `name`
|
4
|
+
class GraphQL::StringNamedHash
|
5
|
+
attr_reader :to_h
|
6
|
+
def initialize(input_hash)
|
7
|
+
@to_h = input_hash
|
8
|
+
.reduce({}) { |memo, (key, value)| memo[key.to_s] = value; memo }
|
9
|
+
# Set the name of the value based on its key
|
10
|
+
@to_h.each {|k, v| v.respond_to?("name=") && v.name = k }
|
11
|
+
end
|
12
|
+
end
|
data/lib/graph_ql/directive.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
class GraphQL::Directive
|
1
|
+
class GraphQL::Directive
|
2
|
+
extend GraphQL::Definable
|
3
|
+
attr_definable :on, :arguments, :name, :description
|
4
|
+
|
2
5
|
LOCATIONS = [
|
3
6
|
ON_OPERATION = :on_operation?,
|
4
7
|
ON_FRAGMENT = :on_fragment?,
|
@@ -8,7 +11,6 @@ class GraphQL::Directive < GraphQL::ObjectType
|
|
8
11
|
define_method(location) { self.on.include?(location) }
|
9
12
|
end
|
10
13
|
|
11
|
-
attr_definable :on, :arguments
|
12
14
|
|
13
15
|
def initialize
|
14
16
|
@arguments = {}
|
@@ -27,12 +29,14 @@ class GraphQL::Directive < GraphQL::ObjectType
|
|
27
29
|
|
28
30
|
def arguments(new_arguments=nil)
|
29
31
|
if !new_arguments.nil?
|
30
|
-
@arguments = new_arguments
|
31
|
-
.reduce({}) {|memo, (k, v)| memo[k.to_s] = v; memo}
|
32
|
-
.each { |k, v| v.respond_to?("name=") && v.name = k}
|
32
|
+
@arguments = GraphQL::StringNamedHash.new(new_arguments).to_h
|
33
33
|
end
|
34
34
|
@arguments
|
35
35
|
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"<GraphQL::Directive #{name}>"
|
39
|
+
end
|
36
40
|
end
|
37
41
|
|
38
42
|
require 'graph_ql/directives/directive_chain'
|
@@ -24,7 +24,7 @@ class GraphQL::DirectiveChain
|
|
24
24
|
@result = block.call
|
25
25
|
else
|
26
26
|
applicable_directives.map do |(ast_directive, directive)|
|
27
|
-
args = GraphQL::Query::Arguments.new(ast_directive.arguments, operation_resolver.variables).to_h
|
27
|
+
args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, operation_resolver.variables).to_h
|
28
28
|
@result = directive.resolve(args, block)
|
29
29
|
end
|
30
30
|
@result ||= {}
|
data/lib/graph_ql/field.rb
CHANGED
@@ -16,11 +16,7 @@ class GraphQL::Field
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def arguments=(new_arguments)
|
19
|
-
|
20
|
-
.reduce({}) { |memo, (key, value)| memo[key.to_s] = value; memo }
|
21
|
-
# Set the name from its context on this type:
|
22
|
-
stringified_arguments.each {|k, v| v.respond_to?("name=") && v.name = k }
|
23
|
-
@arguments = stringified_arguments
|
19
|
+
@arguments = GraphQL::StringNamedHash.new(new_arguments).to_h
|
24
20
|
end
|
25
21
|
|
26
22
|
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Nodes} objects.
|
2
2
|
class GraphQL::Transform < Parslet::Transform
|
3
3
|
# Get syntax classes by shallow name:
|
4
|
-
|
5
|
-
GraphQL::Nodes.const_get(constant_name)
|
6
|
-
end
|
4
|
+
include GraphQL::Nodes
|
7
5
|
|
8
6
|
def self.optional_sequence(name)
|
9
7
|
rule(name => simple(:val)) { [] }
|
data/lib/graph_ql/query.rb
CHANGED
@@ -9,7 +9,7 @@ class GraphQL::Query
|
|
9
9
|
@schema = schema
|
10
10
|
@debug = debug
|
11
11
|
@query_string = query_string
|
12
|
-
@context = context
|
12
|
+
@context = Context.new(context)
|
13
13
|
@params = params
|
14
14
|
@validate = validate
|
15
15
|
@fragments = {}
|
@@ -48,8 +48,7 @@ class GraphQL::Query
|
|
48
48
|
def execute
|
49
49
|
@operations.reduce({}) do |memo, (name, operation)|
|
50
50
|
resolver = OperationResolver.new(operation, self)
|
51
|
-
memo
|
52
|
-
memo
|
51
|
+
memo.merge(resolver.result)
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
@@ -62,6 +61,18 @@ class GraphQL::Query
|
|
62
61
|
end
|
63
62
|
end
|
64
63
|
end
|
64
|
+
|
65
|
+
# Expose some query-specific info to field resolve functions.
|
66
|
+
# It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
|
67
|
+
class Context
|
68
|
+
def initialize(arbitrary_hash)
|
69
|
+
@arbitrary_hash = arbitrary_hash
|
70
|
+
end
|
71
|
+
|
72
|
+
def [](key)
|
73
|
+
@arbitrary_hash[key]
|
74
|
+
end
|
75
|
+
end
|
65
76
|
end
|
66
77
|
|
67
78
|
require 'graph_ql/query/arguments'
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# Creates a plain hash out of arguments, looking up variables if necessary
|
2
2
|
class GraphQL::Query::Arguments
|
3
3
|
attr_reader :to_h
|
4
|
-
def initialize(ast_arguments, variables)
|
4
|
+
def initialize(ast_arguments, argument_hash, variables)
|
5
5
|
@to_h = ast_arguments.reduce({}) do |memo, arg|
|
6
|
-
|
6
|
+
arg_defn = argument_hash[arg.name]
|
7
|
+
value = reduce_value(arg.value, arg_defn, variables)
|
7
8
|
memo[arg.name] = value
|
8
9
|
memo
|
9
10
|
end
|
@@ -11,13 +12,13 @@ class GraphQL::Query::Arguments
|
|
11
12
|
|
12
13
|
private
|
13
14
|
|
14
|
-
def reduce_value(value, variables)
|
15
|
+
def reduce_value(value, arg_defn, variables)
|
15
16
|
if value.is_a?(GraphQL::Nodes::VariableIdentifier)
|
16
17
|
value = variables[value.name]
|
17
18
|
elsif value.is_a?(GraphQL::Nodes::Enum)
|
18
|
-
value = value.name
|
19
|
+
value = arg_defn.type.coerce(value.name)
|
19
20
|
elsif value.is_a?(GraphQL::Nodes::InputObject)
|
20
|
-
value = self.class.new(value.pairs, variables).to_h
|
21
|
+
value = self.class.new(value.pairs, arg_defn.type.input_fields, variables).to_h
|
21
22
|
else
|
22
23
|
value
|
23
24
|
end
|
@@ -2,9 +2,9 @@ class GraphQL::Query::FieldResolutionStrategy
|
|
2
2
|
attr_reader :result, :result_value
|
3
3
|
|
4
4
|
def initialize(ast_field, parent_type, target, operation_resolver)
|
5
|
-
arguments = GraphQL::Query::Arguments.new(ast_field.arguments, operation_resolver.variables).to_h
|
6
5
|
field_name = ast_field.name
|
7
6
|
field = parent_type.fields[field_name] || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
|
7
|
+
arguments = GraphQL::Query::Arguments.new(ast_field.arguments, field.arguments, operation_resolver.variables).to_h
|
8
8
|
value = field.resolve(target, arguments, operation_resolver.context)
|
9
9
|
if value.nil?
|
10
10
|
@result_value = value
|
@@ -40,9 +40,9 @@ class GraphQL::Query::FieldResolutionStrategy
|
|
40
40
|
attr_reader :result
|
41
41
|
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
42
42
|
wrapped_type = field_type.of_type
|
43
|
-
resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
|
44
|
-
strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
|
45
43
|
@result = value.map do |item|
|
44
|
+
resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
|
45
|
+
strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
|
46
46
|
inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
|
47
47
|
inner_strategy.result
|
48
48
|
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -3,16 +3,13 @@ end
|
|
3
3
|
|
4
4
|
require 'graph_ql/static_validation/message'
|
5
5
|
require 'graph_ql/static_validation/arguments_validator'
|
6
|
-
|
7
|
-
require 'graph_ql/static_validation/argument_literals_are_compatible'
|
8
|
-
require 'graph_ql/static_validation/arguments_are_defined'
|
9
|
-
require 'graph_ql/static_validation/required_arguments_are_present'
|
10
|
-
require 'graph_ql/static_validation/fragment_types_exist'
|
11
|
-
require 'graph_ql/static_validation/directives_are_defined'
|
12
|
-
require 'graph_ql/static_validation/fields_are_defined_on_type'
|
13
|
-
require 'graph_ql/static_validation/fields_have_appropriate_selections'
|
14
|
-
require 'graph_ql/static_validation/fields_will_merge'
|
15
|
-
require 'graph_ql/static_validation/fragments_are_used'
|
16
6
|
require 'graph_ql/static_validation/type_stack'
|
17
7
|
require 'graph_ql/static_validation/validator'
|
18
8
|
require 'graph_ql/static_validation/literal_validator'
|
9
|
+
|
10
|
+
rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__)
|
11
|
+
Dir.glob(rules_glob).each do |file|
|
12
|
+
require(file)
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'graph_ql/static_validation/all_rules'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Default rules for {GraphQL::StaticValidation::Validator}
|
2
|
+
#
|
3
|
+
# Order is important here. Some validators return {GraphQL::Visitor::SKIP}
|
4
|
+
# which stops the visit on that node. That way it doesn't try to find fields on types that
|
5
|
+
# don't exist, etc.
|
6
|
+
GraphQL::StaticValidation::ALL_RULES = [
|
7
|
+
GraphQL::StaticValidation::DirectivesAreDefined,
|
8
|
+
GraphQL::StaticValidation::FragmentsAreFinite,
|
9
|
+
GraphQL::StaticValidation::FragmentTypesExist,
|
10
|
+
GraphQL::StaticValidation::FragmentsAreOnCompositeTypes,
|
11
|
+
GraphQL::StaticValidation::FragmentSpreadsArePossible,
|
12
|
+
GraphQL::StaticValidation::FragmentsAreUsed,
|
13
|
+
GraphQL::StaticValidation::FieldsAreDefinedOnType,
|
14
|
+
GraphQL::StaticValidation::FieldsWillMerge,
|
15
|
+
GraphQL::StaticValidation::FieldsHaveAppropriateSelections,
|
16
|
+
GraphQL::StaticValidation::ArgumentsAreDefined,
|
17
|
+
GraphQL::StaticValidation::ArgumentLiteralsAreCompatible,
|
18
|
+
GraphQL::StaticValidation::RequiredArgumentsArePresent,
|
19
|
+
GraphQL::StaticValidation::VariablesAreInputTypes,
|
20
|
+
GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped,
|
21
|
+
GraphQL::StaticValidation::VariablesAreUsedAndDefined,
|
22
|
+
GraphQL::StaticValidation::VariableUsagesAreAllowed,
|
23
|
+
]
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# Test whether `ast_value` is a valid input for `type`
|
2
2
|
class GraphQL::StaticValidation::LiteralValidator
|
3
|
-
include GraphQL::StaticValidation::Message::MessageHelper
|
4
|
-
|
5
|
-
attr_reader :errors
|
6
3
|
def validate(ast_value, type)
|
7
4
|
if type.kind.non_null?
|
8
5
|
(!ast_value.nil?) && validate(ast_value, type.of_type)
|
File without changes
|
data/lib/graph_ql/static_validation/{arguments_are_defined.rb → rules/arguments_are_defined.rb}
RENAMED
@@ -1,10 +1,15 @@
|
|
1
1
|
class GraphQL::StaticValidation::ArgumentsAreDefined < GraphQL::StaticValidation::ArgumentsValidator
|
2
2
|
def validate_node(node, defn, context)
|
3
|
+
skip = nil
|
4
|
+
|
3
5
|
node.arguments.each do |argument|
|
4
6
|
argument_defn = defn.arguments[argument.name]
|
5
7
|
if argument_defn.nil?
|
6
8
|
context.errors << message("#{node.class.name.split("::").last} '#{node.name}' doesn't accept argument #{argument.name}", node)
|
9
|
+
skip = GraphQL::Visitor::SKIP
|
7
10
|
end
|
8
11
|
end
|
12
|
+
|
13
|
+
skip
|
9
14
|
end
|
10
15
|
end
|
data/lib/graph_ql/static_validation/{directives_are_defined.rb → rules/directives_are_defined.rb}
RENAMED
File without changes
|
File without changes
|
File without changes
|
@@ -10,7 +10,7 @@ class GraphQL::StaticValidation::FieldsWillMerge
|
|
10
10
|
}
|
11
11
|
visitor[GraphQL::Nodes::Document].leave << -> (node, parent) {
|
12
12
|
has_selections.each { |node|
|
13
|
-
field_map = gather_fields_by_name(node.selections, {}, context)
|
13
|
+
field_map = gather_fields_by_name(node.selections, {}, [], context)
|
14
14
|
find_conflicts(field_map, context)
|
15
15
|
}
|
16
16
|
}
|
@@ -23,18 +23,26 @@ class GraphQL::StaticValidation::FieldsWillMerge
|
|
23
23
|
comparison = FieldDefinitionComparison.new(name, ast_fields)
|
24
24
|
context.errors.push(*comparison.errors)
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
|
27
|
+
subfield_map = {}
|
28
|
+
visited_fragments = []
|
29
|
+
ast_fields.each do |defn|
|
30
|
+
gather_fields_by_name(defn.selections, subfield_map, visited_fragments, context)
|
28
31
|
end
|
29
32
|
find_conflicts(subfield_map, context)
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
33
|
-
def gather_fields_by_name(fields, field_map, context)
|
36
|
+
def gather_fields_by_name(fields, field_map, visited_fragments, context)
|
34
37
|
fields.each do |field|
|
35
38
|
if field.is_a?(GraphQL::Nodes::InlineFragment)
|
36
39
|
next_fields = field.selections
|
37
40
|
elsif field.is_a?(GraphQL::Nodes::FragmentSpread)
|
41
|
+
if visited_fragments.include?(field.name)
|
42
|
+
next
|
43
|
+
else
|
44
|
+
visited_fragments << field.name
|
45
|
+
end
|
38
46
|
fragment = context.fragments[field.name]
|
39
47
|
next_fields = fragment.selections
|
40
48
|
else
|
@@ -43,7 +51,7 @@ class GraphQL::StaticValidation::FieldsWillMerge
|
|
43
51
|
field_map[name_in_selection].push(field)
|
44
52
|
next_fields = []
|
45
53
|
end
|
46
|
-
gather_fields_by_name(next_fields, field_map, context)
|
54
|
+
gather_fields_by_name(next_fields, field_map, visited_fragments, context)
|
47
55
|
end
|
48
56
|
field_map
|
49
57
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class GraphQL::StaticValidation::FragmentSpreadsArePossible
|
2
|
+
include GraphQL::StaticValidation::Message::MessageHelper
|
3
|
+
|
4
|
+
def validate(context)
|
5
|
+
|
6
|
+
context.visitor[GraphQL::Nodes::InlineFragment] << -> (node, parent) {
|
7
|
+
fragment_parent = context.object_types[-2]
|
8
|
+
fragment_child = context.object_types.last
|
9
|
+
validate_fragment_in_scope(fragment_parent, fragment_child, node, context)
|
10
|
+
}
|
11
|
+
|
12
|
+
spreads_to_validate = []
|
13
|
+
|
14
|
+
context.visitor[GraphQL::Nodes::FragmentSpread] << -> (node, parent) {
|
15
|
+
fragment_parent = context.object_types.last
|
16
|
+
spreads_to_validate << [node, fragment_parent]
|
17
|
+
}
|
18
|
+
|
19
|
+
context.visitor[GraphQL::Nodes::Document].leave << -> (node, parent) {
|
20
|
+
spreads_to_validate.each do |spread_values|
|
21
|
+
node, fragment_parent = spread_values
|
22
|
+
fragment_child_name = context.fragments[node.name].type
|
23
|
+
fragment_child = context.schema.types[fragment_child_name]
|
24
|
+
validate_fragment_in_scope(fragment_parent, fragment_child, node, context)
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validate_fragment_in_scope(parent_type, child_type, node, context)
|
32
|
+
intersecting_types = get_possible_types(parent_type) & get_possible_types(child_type)
|
33
|
+
if intersecting_types.none?
|
34
|
+
name = node.respond_to?(:name) ? " #{node.name}" : ""
|
35
|
+
context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_possible_types(type)
|
40
|
+
if type.kind.wraps?
|
41
|
+
get_possible_types(type.of_type)
|
42
|
+
elsif type.kind.object?
|
43
|
+
[type]
|
44
|
+
elsif type.kind.resolves?
|
45
|
+
type.possible_types
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|