graphql 0.10.0 → 0.10.1
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 +7 -11
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/input_object_type.rb +13 -4
- data/lib/graphql/list_type.rb +7 -3
- data/lib/graphql/non_null_type.rb +4 -0
- data/lib/graphql/query.rb +10 -4
- data/lib/graphql/query/arguments.rb +2 -2
- data/lib/graphql/query/executor.rb +3 -1
- data/lib/graphql/query/literal_input.rb +53 -54
- data/lib/graphql/query/serial_execution/selection_resolution.rb +52 -21
- data/lib/graphql/query/variables.rb +12 -6
- data/lib/graphql/scalar_type.rb +6 -2
- data/lib/graphql/static_validation/literal_validator.rb +3 -4
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +7 -0
- data/spec/graphql/list_type_spec.rb +9 -0
- data/spec/graphql/query/arguments_spec.rb +21 -0
- data/spec/graphql/query/executor_spec.rb +32 -0
- data/spec/graphql/query_spec.rb +22 -2
- data/spec/graphql/scalar_type_spec.rb +24 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +7 -1
- metadata +8 -3
- data/lib/graphql/query/ruby_input.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5651b5fa2ec7dde473a9747b9899bcb99a71b1d5
|
4
|
+
data.tar.gz: a3c045b8d41109f2867090f68c0868eb68671d55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4ca9364a51bc9d85927d3a00f9d06514a386a482078366167f647eb483455d0bb052041a921f0c107e84cb872a26343d26684c6c625a5fcc8ba19132191ffae
|
7
|
+
data.tar.gz: 6c60beeafb45f1246293c4aa559e04a0240ca1e2a9d82f3323f3a34320bd2d5703f3b38aa647ad21b57128a9121cb9ec2a1c213dd0d42400a1bce801a3da31f0
|
data/lib/graphql/base_type.rb
CHANGED
@@ -76,18 +76,14 @@ module GraphQL
|
|
76
76
|
|
77
77
|
alias :inspect :to_s
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def coerce_input!(input_value)
|
84
|
-
coerced_value = coerce_input(input_value)
|
85
|
-
|
86
|
-
if coerced_value.nil?
|
87
|
-
raise GraphQL::ExecutionError.new("Couldn't coerce #{input_value.inspect} to #{self.unwrap.name}")
|
88
|
-
end
|
79
|
+
def valid_input?(value)
|
80
|
+
return true if value.nil?
|
81
|
+
valid_non_null_input?(value)
|
82
|
+
end
|
89
83
|
|
90
|
-
|
84
|
+
def coerce_input(value)
|
85
|
+
return nil if value.nil?
|
86
|
+
coerce_non_null_input(value)
|
91
87
|
end
|
92
88
|
end
|
93
89
|
end
|
data/lib/graphql/enum_type.rb
CHANGED
@@ -41,6 +41,10 @@ class GraphQL::EnumType < GraphQL::BaseType
|
|
41
41
|
GraphQL::TypeKinds::ENUM
|
42
42
|
end
|
43
43
|
|
44
|
+
def valid_non_null_input?(value_name)
|
45
|
+
@values_by_name.key?(value_name)
|
46
|
+
end
|
47
|
+
|
44
48
|
# Get the underlying value for this enum value
|
45
49
|
#
|
46
50
|
# @example get episode value from Enum
|
@@ -49,7 +53,7 @@ class GraphQL::EnumType < GraphQL::BaseType
|
|
49
53
|
#
|
50
54
|
# @param value_name [String] the string representation of this enum value
|
51
55
|
# @return [Object] the underlying value for this enum value
|
52
|
-
def
|
56
|
+
def coerce_non_null_input(value_name)
|
53
57
|
@values_by_name.fetch(value_name).value
|
54
58
|
end
|
55
59
|
|
@@ -19,12 +19,21 @@ class GraphQL::InputObjectType < GraphQL::BaseType
|
|
19
19
|
GraphQL::TypeKinds::INPUT_OBJECT
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def valid_non_null_input?(input)
|
23
|
+
return false unless input.is_a?(Hash) || input.is_a?(GraphQL::Query::Arguments)
|
24
|
+
return false unless input.all? { |name, value| input_fields[name] }
|
25
|
+
input_fields.all? { |name, field| field.type.valid_input?(input[name]) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def coerce_non_null_input(value)
|
23
29
|
input_values = {}
|
24
30
|
input_fields.each do |input_key, input_field_defn|
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
field_value = value[input_key]
|
32
|
+
field_value = input_field_defn.type.coerce_input(field_value)
|
33
|
+
if field_value.nil?
|
34
|
+
field_value = input_field_defn.default_value
|
35
|
+
end
|
36
|
+
input_values[input_key] = field_value unless field_value.nil?
|
28
37
|
end
|
29
38
|
GraphQL::Query::Arguments.new(input_values)
|
30
39
|
end
|
data/lib/graphql/list_type.rb
CHANGED
@@ -17,8 +17,12 @@ class GraphQL::ListType < GraphQL::BaseType
|
|
17
17
|
"[#{of_type.to_s}]"
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
22
|
-
value.
|
20
|
+
def valid_non_null_input?(value)
|
21
|
+
return false unless value.is_a?(Array)
|
22
|
+
value.all?{ |item| of_type.valid_input?(item) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def coerce_non_null_input(value)
|
26
|
+
value.map{ |item| of_type.coerce_input(item) }
|
23
27
|
end
|
24
28
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -6,10 +6,17 @@ class GraphQL::Query
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
class
|
10
|
-
def initialize(
|
11
|
-
msg = "Variable #{name} of type #{type}
|
9
|
+
class VariableValidationError < GraphQL::ExecutionError
|
10
|
+
def initialize(variable_ast, type, reason)
|
11
|
+
msg = "Variable #{variable_ast.name} of type #{type} #{reason}"
|
12
12
|
super(msg)
|
13
|
+
self.ast_node = variable_ast
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class VariableMissingError < VariableValidationError
|
18
|
+
def initialize(variable_ast, type)
|
19
|
+
super(variable_ast, type, "can't be null")
|
13
20
|
end
|
14
21
|
end
|
15
22
|
|
@@ -102,7 +109,6 @@ require 'graphql/query/context'
|
|
102
109
|
require 'graphql/query/directive_chain'
|
103
110
|
require 'graphql/query/executor'
|
104
111
|
require 'graphql/query/literal_input'
|
105
|
-
require 'graphql/query/ruby_input'
|
106
112
|
require 'graphql/query/serial_execution'
|
107
113
|
require 'graphql/query/type_resolver'
|
108
114
|
require 'graphql/query/variables'
|
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
|
9
9
|
def initialize(values)
|
10
10
|
@values = values.inject({}) do |memo, (inner_key, inner_value)|
|
11
|
-
memo[inner_key] = wrap_value(inner_value)
|
11
|
+
memo[inner_key.to_s] = wrap_value(inner_value)
|
12
12
|
memo
|
13
13
|
end
|
14
14
|
end
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
19
19
|
@values[key.to_s]
|
20
20
|
end
|
21
21
|
|
22
|
-
def_delegators :@
|
22
|
+
def_delegators :@values, :keys, :values, :each
|
23
23
|
|
24
24
|
private
|
25
25
|
|
@@ -13,7 +13,9 @@ module GraphQL
|
|
13
13
|
# @return [Hash] A GraphQL response, with either a "data" key or an "errors" key
|
14
14
|
def result
|
15
15
|
execute
|
16
|
-
rescue GraphQL::
|
16
|
+
rescue GraphQL::ExecutionError => err
|
17
|
+
{"errors" => [err.to_h]}
|
18
|
+
rescue GraphQL::Query::OperationNameMissingError => err
|
17
19
|
{"errors" => [{"message" => err.message}]}
|
18
20
|
rescue StandardError => err
|
19
21
|
query.debug && raise(err)
|
@@ -2,47 +2,16 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
# Turn query string values into something useful for query execution
|
4
4
|
class LiteralInput
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
def graphql_value
|
13
|
-
if value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
14
|
-
variables[value.name] # Already cleaned up with RubyInput
|
15
|
-
elsif type.kind.input_object?
|
16
|
-
input_values = {}
|
17
|
-
inner_type = type.unwrap
|
18
|
-
inner_type.input_fields.each do |arg_name, arg_defn|
|
19
|
-
ast_arg = value.pairs.find { |ast_arg| ast_arg.name == arg_name }
|
20
|
-
raw_value = resolve_argument_value(ast_arg, arg_defn, variables)
|
21
|
-
reduced_value = coerce(arg_defn.type, raw_value, variables)
|
22
|
-
input_values[arg_name] = reduced_value
|
23
|
-
end
|
24
|
-
input_values
|
25
|
-
elsif type.kind.list?
|
26
|
-
inner_type = type.of_type
|
27
|
-
value.map { |item| coerce(inner_type, item, variables) }
|
28
|
-
elsif type.kind.non_null?
|
29
|
-
inner_type = type.of_type
|
30
|
-
coerce(inner_type, value, variables)
|
31
|
-
elsif type.kind.scalar?
|
32
|
-
type.coerce_input!(value)
|
33
|
-
elsif type.kind.enum?
|
34
|
-
value_name = value.name # it's a Nodes::Enum
|
35
|
-
type.coerce_input!(value_name)
|
5
|
+
def self.coerce(type, value, variables)
|
6
|
+
if value.is_a?(Language::Nodes::VariableIdentifier)
|
7
|
+
variables[value.name]
|
8
|
+
elsif value.nil?
|
9
|
+
nil
|
36
10
|
else
|
37
|
-
|
11
|
+
LiteralKindCoercers::STRATEGIES.fetch(type.kind).coerce(value, type, variables)
|
38
12
|
end
|
39
13
|
end
|
40
14
|
|
41
|
-
def self.coerce(type, value, variables)
|
42
|
-
input = self.new(type, value, variables)
|
43
|
-
input.graphql_value
|
44
|
-
end
|
45
|
-
|
46
15
|
def self.from_arguments(ast_arguments, argument_defns, variables)
|
47
16
|
values_hash = {}
|
48
17
|
argument_defns.each do |arg_name, arg_defn|
|
@@ -59,31 +28,61 @@ module GraphQL
|
|
59
28
|
GraphQL::Query::Arguments.new(values_hash)
|
60
29
|
end
|
61
30
|
|
62
|
-
|
31
|
+
module LiteralKindCoercers
|
32
|
+
module NonNullLiteral
|
33
|
+
def self.coerce(value, type, variables)
|
34
|
+
LiteralInput.coerce(type.of_type, value, variables)
|
35
|
+
end
|
36
|
+
end
|
63
37
|
|
64
|
-
|
65
|
-
|
66
|
-
|
38
|
+
module ListLiteral
|
39
|
+
def self.coerce(value, type, variables)
|
40
|
+
if value.is_a?(Array)
|
41
|
+
value.map{ |element_ast| LiteralInput.coerce(type.of_type, element_ast, variables) }
|
42
|
+
else
|
43
|
+
[LiteralInput.coerce(type.of_type, value, variables)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
67
47
|
|
68
|
-
|
69
|
-
|
70
|
-
|
48
|
+
module InputObjectLiteral
|
49
|
+
def self.coerce(value, type, variables)
|
50
|
+
hash = {}
|
51
|
+
value.pairs.each do |arg|
|
52
|
+
field_type = type.input_fields[arg.name].type
|
53
|
+
hash[arg.name] = LiteralInput.coerce(field_type, arg.value, variables)
|
54
|
+
end
|
55
|
+
type.input_fields.each do |arg_name, arg_defn|
|
56
|
+
if hash[arg_name].nil?
|
57
|
+
value = LiteralInput.coerce(arg_defn.type, arg_defn.default_value, variables)
|
58
|
+
hash[arg_name] = value unless value.nil?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
Arguments.new(hash)
|
62
|
+
end
|
63
|
+
end
|
71
64
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
def self.resolve_argument_value(ast_arg, arg_defn, variables)
|
77
|
-
if !ast_arg.nil?
|
78
|
-
raw_value = ast_arg.value
|
65
|
+
module EnumLiteral
|
66
|
+
def self.coerce(value, type, variables)
|
67
|
+
type.coerce_input(value.name)
|
68
|
+
end
|
79
69
|
end
|
80
70
|
|
81
|
-
|
82
|
-
|
71
|
+
module ScalarLiteral
|
72
|
+
def self.coerce(value, type, variables)
|
73
|
+
type.coerce_input(value)
|
74
|
+
end
|
83
75
|
end
|
84
76
|
|
85
|
-
|
77
|
+
STRATEGIES = {
|
78
|
+
TypeKinds::NON_NULL => NonNullLiteral,
|
79
|
+
TypeKinds::LIST => ListLiteral,
|
80
|
+
TypeKinds::INPUT_OBJECT => InputObjectLiteral,
|
81
|
+
TypeKinds::ENUM => EnumLiteral,
|
82
|
+
TypeKinds::SCALAR => ScalarLiteral,
|
83
|
+
}
|
86
84
|
end
|
85
|
+
private_constant :LiteralKindCoercers
|
87
86
|
end
|
88
87
|
end
|
89
88
|
end
|
@@ -19,14 +19,10 @@ module GraphQL
|
|
19
19
|
# Then, In a second pass, we resolve the flattened set of fields
|
20
20
|
selections
|
21
21
|
.reduce({}){|memo, ast_node|
|
22
|
-
flatten_selection(ast_node)
|
23
|
-
|
24
|
-
|
25
|
-
else
|
26
|
-
memo[name] = selection
|
27
|
-
end
|
22
|
+
flattened_selections = flatten_selection(ast_node)
|
23
|
+
flattened_selections.each do |name, selection|
|
24
|
+
merge_into_result(memo, selection)
|
28
25
|
end
|
29
|
-
|
30
26
|
memo
|
31
27
|
}
|
32
28
|
.values
|
@@ -38,28 +34,49 @@ module GraphQL
|
|
38
34
|
private
|
39
35
|
|
40
36
|
def flatten_selection(ast_node)
|
41
|
-
|
37
|
+
strategy_method = STRATEGIES[ast_node.class]
|
38
|
+
send(strategy_method, ast_node)
|
39
|
+
end
|
40
|
+
|
41
|
+
STRATEGIES = {
|
42
|
+
GraphQL::Language::Nodes::Field => :flatten_field,
|
43
|
+
GraphQL::Language::Nodes::InlineFragment => :flatten_inline_fragment,
|
44
|
+
GraphQL::Language::Nodes::FragmentSpread => :flatten_fragment_spread,
|
45
|
+
}
|
42
46
|
|
43
|
-
|
44
|
-
|
47
|
+
def flatten_field(ast_node)
|
48
|
+
result_name = ast_node.alias || ast_node.name
|
49
|
+
return { result_name => ast_node }
|
50
|
+
end
|
45
51
|
|
52
|
+
def flatten_inline_fragment(ast_node)
|
46
53
|
chain = GraphQL::Query::DirectiveChain.new(ast_node, query) {
|
47
|
-
|
48
|
-
memo.merge(flatten_selection(selection))
|
49
|
-
end
|
54
|
+
flatten_fragment(ast_node)
|
50
55
|
}
|
56
|
+
chain.result
|
57
|
+
end
|
51
58
|
|
52
|
-
|
59
|
+
def flatten_fragment_spread(ast_node)
|
60
|
+
ast_fragment_defn = query.fragments[ast_node.name]
|
61
|
+
chain = GraphQL::Query::DirectiveChain.new(ast_node, query) {
|
62
|
+
flatten_fragment(ast_fragment_defn)
|
63
|
+
}
|
64
|
+
chain.result
|
53
65
|
end
|
54
66
|
|
55
|
-
def
|
56
|
-
if
|
57
|
-
|
58
|
-
elsif ast_node.is_a? GraphQL::Language::Nodes::InlineFragment
|
59
|
-
ast_node
|
60
|
-
else
|
61
|
-
raise 'Unrecognized fragment node'
|
67
|
+
def flatten_fragment(ast_fragment)
|
68
|
+
if !fragment_type_can_apply?(ast_fragment)
|
69
|
+
return {}
|
62
70
|
end
|
71
|
+
|
72
|
+
flat_result = {}
|
73
|
+
ast_fragment.selections.each do |selection|
|
74
|
+
flat_selection = flatten_selection(selection)
|
75
|
+
flat_selection.each do |name, selection|
|
76
|
+
merge_into_result(flat_result, selection)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
flat_result
|
63
80
|
end
|
64
81
|
|
65
82
|
def fragment_type_can_apply?(ast_fragment)
|
@@ -93,6 +110,20 @@ module GraphQL
|
|
93
110
|
}
|
94
111
|
chain.result
|
95
112
|
end
|
113
|
+
|
114
|
+
def merge_into_result(memo, selection)
|
115
|
+
name = if selection.respond_to?(:alias)
|
116
|
+
selection.alias || selection.name
|
117
|
+
else
|
118
|
+
selection.name
|
119
|
+
end
|
120
|
+
|
121
|
+
if memo.has_key?(name)
|
122
|
+
memo[name] = merge_fields(memo[name], selection)
|
123
|
+
else
|
124
|
+
memo[name] = selection
|
125
|
+
end
|
126
|
+
end
|
96
127
|
end
|
97
128
|
end
|
98
129
|
end
|
@@ -26,12 +26,18 @@ module GraphQL
|
|
26
26
|
variable_name = ast_variable.name
|
27
27
|
default_value = ast_variable.default_value
|
28
28
|
provided_value = @provided_variables[variable_name]
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
|
30
|
+
unless variable_type.valid_input?(provided_value)
|
31
|
+
if provided_value.nil?
|
32
|
+
raise GraphQL::Query::VariableMissingError.new(ast_variable, variable_type)
|
33
|
+
else
|
34
|
+
raise GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, "was provided invalid value #{JSON.dump(provided_value)}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
if provided_value.nil?
|
38
|
+
GraphQL::Query::LiteralInput.coerce(variable_type, default_value, {})
|
39
|
+
else
|
40
|
+
variable_type.coerce_input(provided_value)
|
35
41
|
end
|
36
42
|
end
|
37
43
|
end
|
data/lib/graphql/scalar_type.rb
CHANGED
@@ -2,7 +2,7 @@ module GraphQL
|
|
2
2
|
# The parent type for scalars, eg {GraphQL::STRING_TYPE}, {GraphQL::INT_TYPE}
|
3
3
|
#
|
4
4
|
# @example defining a type for Time
|
5
|
-
# TimeType = GraphQL::
|
5
|
+
# TimeType = GraphQL::ScalarType.define do
|
6
6
|
# name "Time"
|
7
7
|
# description "Time since epoch in seconds"
|
8
8
|
#
|
@@ -19,7 +19,11 @@ module GraphQL
|
|
19
19
|
self.coerce_result = proc
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def valid_non_null_input?(value)
|
23
|
+
!coerce_non_null_input(value).nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def coerce_non_null_input(value)
|
23
27
|
@coerce_input_proc.call(value)
|
24
28
|
end
|
25
29
|
|
@@ -6,10 +6,10 @@ class GraphQL::StaticValidation::LiteralValidator
|
|
6
6
|
elsif type.kind.list? && ast_value.is_a?(Array)
|
7
7
|
item_type = type.of_type
|
8
8
|
ast_value.all? { |val| validate(val, item_type) }
|
9
|
-
elsif type.kind.scalar?
|
10
|
-
|
9
|
+
elsif type.kind.scalar? && !ast_value.is_a?(GraphQL::Language::Nodes::AbstractNode) && !ast_value.is_a?(Array)
|
10
|
+
type.valid_input?(ast_value)
|
11
11
|
elsif type.kind.enum? && ast_value.is_a?(GraphQL::Language::Nodes::Enum)
|
12
|
-
|
12
|
+
type.valid_input?(ast_value.name)
|
13
13
|
elsif type.kind.input_object? && ast_value.is_a?(GraphQL::Language::Nodes::InputObject)
|
14
14
|
fields = type.input_fields
|
15
15
|
ast_value.pairs.all? do |value|
|
@@ -18,7 +18,6 @@ class GraphQL::StaticValidation::LiteralValidator
|
|
18
18
|
present_if_required && validate(value.value, field_type)
|
19
19
|
end
|
20
20
|
elsif ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
21
|
-
# Todo: somehow pass in the document's variable definitions and validate this
|
22
21
|
true
|
23
22
|
else
|
24
23
|
false
|
@@ -17,7 +17,7 @@ class GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped
|
|
17
17
|
else
|
18
18
|
type = context.schema.type_from_ast(node.type)
|
19
19
|
if !literal_validator.validate(value, type)
|
20
|
-
context.errors << message("Default value for $#{node.name} doesn't match type #{
|
20
|
+
context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -109,6 +109,13 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
109
109
|
- Big ideas:
|
110
110
|
- Use [graphql-parser](https://github.com/shopify/graphql-parser) (Ruby bindings for [libgraphqlparser](https://github.com/graphql/libgraphqlparser)) instead of Parslet
|
111
111
|
- Revamp the fixture Schema to be more useful (better names, more extensible)
|
112
|
+
- __Subscriptions__
|
113
|
+
- This is a good chance to make an `Operation` abstraction of which `query`, `mutation` and `subscription` are members
|
114
|
+
- For a subscription, `graphql` would send an outbound message to the system (allow the host application to manage its own subscriptions via Pusher, ActionCable, whatever)
|
115
|
+
- Pre-process query strings?
|
116
|
+
- Remove `@skip`-ed things
|
117
|
+
- Inline any fragments
|
118
|
+
- Inline variables?
|
112
119
|
|
113
120
|
## Goals
|
114
121
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::ListType do
|
4
|
+
let(:float_list) { GraphQL::ListType.new(of_type: GraphQL::FLOAT_TYPE) }
|
5
|
+
|
6
|
+
it 'coerces elements in the list' do
|
7
|
+
assert_equal([1.0, 2.0, 3.0].inspect, float_list.coerce_input([1, 2, 3]).inspect)
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Query::Arguments do
|
4
|
+
let(:arguments) { GraphQL::Query::Arguments.new({ a: 1, b: 2 }) }
|
5
|
+
|
6
|
+
it 'returns keys as strings' do
|
7
|
+
assert_equal(['a', 'b'], arguments.keys)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'delegates values to values hash' do
|
11
|
+
assert_equal([1, 2], arguments.values)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'delegates each to values hash' do
|
15
|
+
pairs = []
|
16
|
+
arguments.each do |key, value|
|
17
|
+
pairs << [key, value]
|
18
|
+
end
|
19
|
+
assert_equal([['a', 1], ['b', 2]], pairs)
|
20
|
+
end
|
21
|
+
end
|
@@ -189,5 +189,37 @@ describe GraphQL::Query::Executor do
|
|
189
189
|
assert_equal(expected, result)
|
190
190
|
end
|
191
191
|
end
|
192
|
+
|
193
|
+
describe "for required input object fields" do
|
194
|
+
let(:variables) { {"input" => {} } }
|
195
|
+
let(:query_string) {%| mutation M($input: ReplaceValuesInput!) { replaceValues(input: $input) } |}
|
196
|
+
it "returns a variable validation error" do
|
197
|
+
expected = {
|
198
|
+
"errors"=>[
|
199
|
+
{
|
200
|
+
"message" => "Variable input of type ReplaceValuesInput! was provided invalid value {}",
|
201
|
+
"locations" => [{"line"=>1, "column"=>14}]
|
202
|
+
}
|
203
|
+
]
|
204
|
+
}
|
205
|
+
assert_equal(expected, result)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "for input objects with unknown keys in value" do
|
210
|
+
let(:variables) { {"input" => [{ "foo" => "bar" }]} }
|
211
|
+
let(:query_string) {%| query Q($input: [DairyProductInput]) { searchDairy(product: $input) { __typename, ... on Cheese { id, source } } } |}
|
212
|
+
it "returns a variable validation error" do
|
213
|
+
expected = {
|
214
|
+
"errors"=>[
|
215
|
+
{
|
216
|
+
"message" => "Variable input of type [DairyProductInput] was provided invalid value [{\"foo\":\"bar\"}]",
|
217
|
+
"locations" => [{"line"=>1, "column"=>11}]
|
218
|
+
}
|
219
|
+
]
|
220
|
+
}
|
221
|
+
assert_equal(expected, result)
|
222
|
+
end
|
223
|
+
end
|
192
224
|
end
|
193
225
|
end
|
data/spec/graphql/query_spec.rb
CHANGED
@@ -103,6 +103,7 @@ describe GraphQL::Query do
|
|
103
103
|
describe "merging fragments with different keys" do
|
104
104
|
let(:query_string) { %|
|
105
105
|
query getCheeseFieldsThroughDairy {
|
106
|
+
... cheeseFrag3
|
106
107
|
dairy {
|
107
108
|
...flavorFragment
|
108
109
|
...fatContentFragment
|
@@ -124,6 +125,21 @@ describe GraphQL::Query do
|
|
124
125
|
fatContent
|
125
126
|
}
|
126
127
|
}
|
128
|
+
|
129
|
+
fragment cheeseFrag1 on Query {
|
130
|
+
cheese(id: 1) {
|
131
|
+
id
|
132
|
+
}
|
133
|
+
}
|
134
|
+
fragment cheeseFrag2 on Query {
|
135
|
+
cheese(id: 1) {
|
136
|
+
flavor
|
137
|
+
}
|
138
|
+
}
|
139
|
+
fragment cheeseFrag3 on Query {
|
140
|
+
... cheeseFrag2
|
141
|
+
... cheeseFrag1
|
142
|
+
}
|
127
143
|
|}
|
128
144
|
|
129
145
|
it "should include keys from each fragment" do
|
@@ -139,7 +155,11 @@ describe GraphQL::Query do
|
|
139
155
|
"fatContent" => 0.04,
|
140
156
|
}
|
141
157
|
],
|
142
|
-
}
|
158
|
+
},
|
159
|
+
"cheese" => {
|
160
|
+
"id" => 1,
|
161
|
+
"flavor" => "Brie"
|
162
|
+
},
|
143
163
|
}}
|
144
164
|
assert_equal(expected, result)
|
145
165
|
end
|
@@ -224,7 +244,7 @@ describe GraphQL::Query do
|
|
224
244
|
let(:query_variables) { {"cheeseId" => "2"} }
|
225
245
|
|
226
246
|
it "raises an error" do
|
227
|
-
|
247
|
+
assert_equal(result["errors"][0]["message"], %{Variable cheeseId of type Int! was provided invalid value "2"})
|
228
248
|
end
|
229
249
|
end
|
230
250
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::ScalarType do
|
4
|
+
let(:scalar) {
|
5
|
+
GraphQL::ScalarType.define do
|
6
|
+
name "BigInt"
|
7
|
+
coerce_input ->(value) { Integer(value) }
|
8
|
+
coerce_result ->(value) { value.to_s }
|
9
|
+
end
|
10
|
+
}
|
11
|
+
let(:bignum) { 2 ** 128 }
|
12
|
+
|
13
|
+
it 'coerces nil into nil' do
|
14
|
+
assert_equal(nil, scalar.coerce_input(nil))
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'coerces input into objects' do
|
18
|
+
assert_equal(bignum, scalar.coerce_input(bignum.to_s))
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'coerces result value for serialization' do
|
22
|
+
assert_equal(bignum.to_s, scalar.coerce_result(bignum))
|
23
|
+
end
|
24
|
+
end
|
@@ -18,7 +18,7 @@ describe GraphQL::StaticValidation::ArgumentLiteralsAreCompatible do
|
|
18
18
|
let(:errors) { validator.validate(document) }
|
19
19
|
|
20
20
|
it 'finds undefined arguments to fields and directives' do
|
21
|
-
assert_equal(
|
21
|
+
assert_equal(4, errors.length)
|
22
22
|
|
23
23
|
query_root_error = {
|
24
24
|
"message"=>"Argument id on Field 'cheese' has an invalid value",
|
@@ -26,6 +26,12 @@ describe GraphQL::StaticValidation::ArgumentLiteralsAreCompatible do
|
|
26
26
|
}
|
27
27
|
assert_includes(errors, query_root_error)
|
28
28
|
|
29
|
+
directive_error = {
|
30
|
+
"message"=>"Argument if on Directive 'skip' has an invalid value",
|
31
|
+
"locations"=>[{"line"=>4, "column"=>31}]
|
32
|
+
}
|
33
|
+
assert_includes(errors, directive_error)
|
34
|
+
|
29
35
|
input_object_error = {
|
30
36
|
"message"=>"Argument product on Field 'searchDairy' has an invalid value",
|
31
37
|
"locations"=>[{"line"=>6, "column"=>7}]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
4
|
+
version: 0.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parslet
|
@@ -214,7 +214,6 @@ files:
|
|
214
214
|
- lib/graphql/query/directive_chain.rb
|
215
215
|
- lib/graphql/query/executor.rb
|
216
216
|
- lib/graphql/query/literal_input.rb
|
217
|
-
- lib/graphql/query/ruby_input.rb
|
218
217
|
- lib/graphql/query/serial_execution.rb
|
219
218
|
- lib/graphql/query/serial_execution/field_resolution.rb
|
220
219
|
- lib/graphql/query/serial_execution/operation_resolution.rb
|
@@ -277,12 +276,15 @@ files:
|
|
277
276
|
- spec/graphql/language/parser_spec.rb
|
278
277
|
- spec/graphql/language/transform_spec.rb
|
279
278
|
- spec/graphql/language/visitor_spec.rb
|
279
|
+
- spec/graphql/list_type_spec.rb
|
280
280
|
- spec/graphql/object_type_spec.rb
|
281
|
+
- spec/graphql/query/arguments_spec.rb
|
281
282
|
- spec/graphql/query/base_execution/value_resolution_spec.rb
|
282
283
|
- spec/graphql/query/context_spec.rb
|
283
284
|
- spec/graphql/query/executor_spec.rb
|
284
285
|
- spec/graphql/query/type_resolver_spec.rb
|
285
286
|
- spec/graphql/query_spec.rb
|
287
|
+
- spec/graphql/scalar_type_spec.rb
|
286
288
|
- spec/graphql/schema/field_validator_spec.rb
|
287
289
|
- spec/graphql/schema/middleware_chain_spec.rb
|
288
290
|
- spec/graphql/schema/printer_spec.rb
|
@@ -355,12 +357,15 @@ test_files:
|
|
355
357
|
- spec/graphql/language/parser_spec.rb
|
356
358
|
- spec/graphql/language/transform_spec.rb
|
357
359
|
- spec/graphql/language/visitor_spec.rb
|
360
|
+
- spec/graphql/list_type_spec.rb
|
358
361
|
- spec/graphql/object_type_spec.rb
|
362
|
+
- spec/graphql/query/arguments_spec.rb
|
359
363
|
- spec/graphql/query/base_execution/value_resolution_spec.rb
|
360
364
|
- spec/graphql/query/context_spec.rb
|
361
365
|
- spec/graphql/query/executor_spec.rb
|
362
366
|
- spec/graphql/query/type_resolver_spec.rb
|
363
367
|
- spec/graphql/query_spec.rb
|
368
|
+
- spec/graphql/scalar_type_spec.rb
|
364
369
|
- spec/graphql/schema/field_validator_spec.rb
|
365
370
|
- spec/graphql/schema/middleware_chain_spec.rb
|
366
371
|
- spec/graphql/schema/printer_spec.rb
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module GraphQL
|
2
|
-
class Query
|
3
|
-
# Turn Ruby values into something useful for query execution
|
4
|
-
class RubyInput
|
5
|
-
def initialize(type, incoming_value)
|
6
|
-
@type = type
|
7
|
-
@incoming_value = incoming_value
|
8
|
-
end
|
9
|
-
|
10
|
-
def graphql_value
|
11
|
-
@type.coerce_input!(@incoming_value)
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.coerce(type, value)
|
15
|
-
input = self.new(type, value)
|
16
|
-
input.graphql_value
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|