graphql 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/language/parser.rb +11 -2
- data/lib/graphql/language/transform.rb +11 -1
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/schema/each_item_validator.rb +8 -3
- data/lib/graphql/schema/implementation_validator.rb +9 -3
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +35 -2
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/language/parser_spec.rb +6 -3
- data/spec/graphql/language/transform_spec.rb +4 -4
- data/spec/graphql/schema/type_validator_spec.rb +10 -0
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +16 -6
- data/spec/support/dairy_app.rb +0 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16c0fb0de67cb50d74c16528abcccb26ad91b01f
|
4
|
+
data.tar.gz: 008d40ba5dca1c8fb2c8800f987f02981c64d6f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2573ce8d132b479472db3a680861289e996ad213c6173ad1fadac0ed3377219a7cc41cf7089149b60e81b2dc75fe5c50a38fc6af51e201744b129fc2bf934019
|
7
|
+
data.tar.gz: 79877bafa23685bedf218e78f9572a1795a30383433943586842ba7605696a71eb391fcf429cc66f6992a9ff29121fff6086d9b428d925541a0f655ef20c90f5
|
@@ -95,8 +95,17 @@ module GraphQL::Language
|
|
95
95
|
rule(:value_input_object) { str("{") >> value_input_object_pair.repeat(1).as(:input_object) >> str("}") }
|
96
96
|
rule(:value_input_object_pair) { space? >> name.as(:input_object_name) >> space? >> str(":") >> space? >> value.as(:input_object_value) >> separator? }
|
97
97
|
rule(:value_int) { (value_sign? >> match('\d').repeat(1)).as(:int) }
|
98
|
-
|
99
|
-
rule(:
|
98
|
+
rule(:value_string) { str('"') >> value_string_char.repeat.as(:string) >> str('"')}
|
99
|
+
rule(:value_string_char) { value_string_escaped_char | value_string_escaped_unicode | value_string_source_char}
|
100
|
+
rule(:value_string_escaped_char) { str("\\") >> match('["\/bfnrt]') }
|
101
|
+
rule(:value_string_escaped_unicode) { str("\\") >> match('u[\dA-Fa-f]{4}')}
|
102
|
+
rule(:value_string_source_char) { (str('"') | str("\\") | value_string_line_terminator).absent? >> any }
|
103
|
+
rule(:value_string_line_terminator) {
|
104
|
+
str('\u000A') | # new line
|
105
|
+
str('\u000D') | # carriage return
|
106
|
+
str('\u2028') | # line separator
|
107
|
+
str('\u2029') # paragraph separator
|
108
|
+
}
|
100
109
|
rule(:value_enum) { name.as(:enum) }
|
101
110
|
rule(:value_variable) { str("$") >> name.as(:variable) }
|
102
111
|
|
@@ -91,7 +91,17 @@ module GraphQL::Language
|
|
91
91
|
rule(input_object_name: simple(:n), input_object_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
|
92
92
|
rule(int: simple(:v)) { v.to_i }
|
93
93
|
rule(float: simple(:v)) { v.to_f }
|
94
|
-
|
94
|
+
|
95
|
+
ESCAPES = /\\(["\\\/bfnrt])/
|
96
|
+
UTF_8 = /\\u[\da-f]{4}/i
|
97
|
+
UTF_8_REPLACE = -> (m) { [m[-4..-1].to_i(16)].pack('U') }
|
98
|
+
|
99
|
+
rule(string: simple(:v)) {
|
100
|
+
string = v.to_s
|
101
|
+
string.gsub!(ESCAPES, '\1')
|
102
|
+
string.gsub!(UTF_8, &UTF_8_REPLACE)
|
103
|
+
string
|
104
|
+
}
|
95
105
|
rule(variable: simple(:v)) { create_node(:VariableIdentifier, name: v.to_s, position_source: v) }
|
96
106
|
rule(enum: simple(:v)) { create_node(:Enum, name: v.to_s, position_source: v)}
|
97
107
|
end
|
@@ -4,9 +4,14 @@ class GraphQL::Schema::EachItemValidator
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def validate(items, as:, must_be:)
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
if !items.is_a?(Array)
|
8
|
+
@errors << "#{as} must be an Array, not #{items.inspect}"
|
9
|
+
return
|
10
|
+
else
|
11
|
+
invalid_items = items.select {|k| !yield(k) }
|
12
|
+
if invalid_items.any?
|
13
|
+
@errors << "#{as} must be #{must_be}, but some aren't: #{invalid_items.map(&:to_s).join(", ")}"
|
14
|
+
end
|
10
15
|
end
|
11
16
|
end
|
12
17
|
end
|
@@ -11,11 +11,17 @@ class GraphQL::Schema::ImplementationValidator
|
|
11
11
|
# If `block_given?`, yield the return value of that method
|
12
12
|
# If provided, use `as` in the error message, overriding class-level `as`.
|
13
13
|
def must_respond_to(method_name, args: [], as: nil)
|
14
|
+
local_as = as || implementation_as
|
15
|
+
method_signature = "##{method_name}(#{args.join(", ")})"
|
14
16
|
if !object.respond_to?(method_name)
|
15
|
-
|
16
|
-
errors << "#{object.to_s} must respond to ##{method_name}(#{args.join(", ")}) to be a #{local_as}"
|
17
|
+
errors << "#{object.to_s} must respond to #{method_signature} to be a #{local_as}"
|
17
18
|
elsif block_given?
|
18
|
-
|
19
|
+
return_value = object.public_send(method_name)
|
20
|
+
if return_value.nil?
|
21
|
+
errors << "#{object.to_s} must return a value for #{method_signature} to be a #{local_as}"
|
22
|
+
else
|
23
|
+
yield(return_value)
|
24
|
+
end
|
19
25
|
end
|
20
26
|
end
|
21
27
|
end
|
@@ -30,8 +30,17 @@ class GraphQL::StaticValidation::VariableUsagesAreAllowed
|
|
30
30
|
end
|
31
31
|
|
32
32
|
arg_defn = arguments[arg_node.name]
|
33
|
-
|
34
|
-
|
33
|
+
arg_defn_type = arg_defn.type
|
34
|
+
|
35
|
+
var_inner_type = var_type.kind.unwrap(var_type)
|
36
|
+
arg_inner_type = arg_defn_type.kind.unwrap(arg_defn_type)
|
37
|
+
|
38
|
+
if var_inner_type != arg_inner_type
|
39
|
+
context.errors << create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node)
|
40
|
+
elsif list_dimension(var_type) != list_dimension(arg_defn_type)
|
41
|
+
context.errors << create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node)
|
42
|
+
elsif !non_null_levels_match(arg_defn_type, var_type)
|
43
|
+
context.errors << create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node)
|
35
44
|
end
|
36
45
|
end
|
37
46
|
|
@@ -44,4 +53,28 @@ class GraphQL::StaticValidation::VariableUsagesAreAllowed
|
|
44
53
|
types[ast_type.name]
|
45
54
|
end
|
46
55
|
end
|
56
|
+
|
57
|
+
def create_error(error_message, var_type, ast_var, arg_defn, arg_node)
|
58
|
+
message("#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})", arg_node)
|
59
|
+
end
|
60
|
+
|
61
|
+
def list_dimension(type)
|
62
|
+
if type.kind.list?
|
63
|
+
1 + list_dimension(type.of_type)
|
64
|
+
elsif type.kind.non_null?
|
65
|
+
list_dimension(type.of_type)
|
66
|
+
else
|
67
|
+
0
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def non_null_levels_match(arg_type, var_type)
|
72
|
+
if arg_type.kind.non_null? && !var_type.kind.non_null?
|
73
|
+
false
|
74
|
+
elsif arg_type.kind.wraps? && var_type.kind.wraps?
|
75
|
+
non_null_levels_match(arg_type.of_type, var_type.of_type)
|
76
|
+
else
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
47
80
|
end
|
data/lib/graphql/version.rb
CHANGED
@@ -59,7 +59,7 @@ describe GraphQL::Language::Parser do
|
|
59
59
|
|
60
60
|
it 'parses directives' do
|
61
61
|
assert(parser.directives.parse_with_debug("@doSomething"), 'gets without argument')
|
62
|
-
assert(parser.directives.parse_with_debug('@doSomething(why: "forSomeReason")'), 'gets with argument')
|
62
|
+
assert(parser.directives.parse_with_debug('@doSomething(why: "\"forSomeReason\"")'), 'gets with argument')
|
63
63
|
assert(parser.directives.parse_with_debug('@myFlag, @doSomething(why: "forSomeReason")'), 'gets multiple')
|
64
64
|
end
|
65
65
|
|
@@ -67,7 +67,7 @@ describe GraphQL::Language::Parser do
|
|
67
67
|
assert(parser.field.parse_with_debug(%|myField { name, id }|), 'gets subselections')
|
68
68
|
assert(parser.field.parse_with_debug(%{myAlias: myField}), 'gets an alias')
|
69
69
|
assert(parser.field.parse_with_debug(%{myField(intKey: 1, floatKey: 1.1e5)}), 'gets arguments')
|
70
|
-
assert(parser.field.parse_with_debug(
|
70
|
+
assert(parser.field.parse_with_debug('myAlias: myField(stringKey: "\"my_string\"", boolKey: false, objKey: {key : true})'), 'gets alias and arguments')
|
71
71
|
assert(parser.field.parse_with_debug(%|myField @withFlag, @skip(if: true) { name, id }|), 'gets with directive')
|
72
72
|
end
|
73
73
|
|
@@ -100,7 +100,10 @@ describe GraphQL::Language::Parser do
|
|
100
100
|
end
|
101
101
|
|
102
102
|
it 'gets strings' do
|
103
|
-
assert(parser.value.parse_with_debug('"my string"'))
|
103
|
+
assert(parser.value.parse_with_debug('"my string"'), "plain strings")
|
104
|
+
assert(parser.value.parse_with_debug('""'), "empty strings")
|
105
|
+
assert(parser.value.parse_with_debug('"\"Hi!\"\n"'), "escaped strings")
|
106
|
+
assert(parser.value.parse_with_debug('"\u0025\u0026"'), "escaped unicode")
|
104
107
|
end
|
105
108
|
|
106
109
|
it 'gets arrays' do
|
@@ -99,20 +99,20 @@ describe GraphQL::Language::Transform do
|
|
99
99
|
assert_equal("SO_COOL", res.arguments[1].value.name)
|
100
100
|
assert_equal({"nice" => {"very" => true}}, res.arguments[2].value.to_h)
|
101
101
|
|
102
|
-
res = get_result(
|
102
|
+
res = get_result('me @flag, @include(if: "\"something\"") {name, id}', parse: :field)
|
103
103
|
assert_equal("me", res.name)
|
104
104
|
assert_equal(nil, res.alias)
|
105
105
|
assert_equal(2, res.directives.length)
|
106
106
|
assert_equal("flag", res.directives.first.name)
|
107
|
-
assert_equal("something", res.directives.last.arguments.first.value)
|
107
|
+
assert_equal('"something"', res.directives.last.arguments.first.value)
|
108
108
|
assert_equal(2, res.selections.length)
|
109
109
|
end
|
110
110
|
|
111
111
|
it 'transforms directives' do
|
112
|
-
res = get_result(
|
112
|
+
res = get_result('@doSomething(vigorously: "\"true\u0025\"")', parse: :directive)
|
113
113
|
assert_equal("doSomething", res.name, 'gets the name without @')
|
114
114
|
assert_equal("vigorously", res.arguments.first.name)
|
115
|
-
assert_equal(true, res.arguments.first.value)
|
115
|
+
assert_equal('"true%"', res.arguments.first.value)
|
116
116
|
|
117
117
|
res = get_result("@someFlag", parse: :directive)
|
118
118
|
assert_equal("someFlag", res.name)
|
@@ -28,6 +28,16 @@ describe GraphQL::Schema::TypeValidator do
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
describe 'when a method returns nil' do
|
32
|
+
let(:type_defn) { base_type_defn.merge(interfaces: nil)}
|
33
|
+
it 'requires name' do
|
34
|
+
assert_equal(
|
35
|
+
["InvalidType must return a value for #interfaces() to be a OBJECT"],
|
36
|
+
errors
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
31
41
|
describe "when a field name isnt a string" do
|
32
42
|
let(:type_defn) { base_type_defn.merge(fields: {symbol_field: (GraphQL::Field.new {|f|}) }) }
|
33
43
|
it "requires string names" do
|
@@ -9,6 +9,7 @@ describe GraphQL::StaticValidation::VariableUsagesAreAllowed do
|
|
9
9
|
$badStr: String!,
|
10
10
|
$goodAnimals: [DairyAnimal!]!,
|
11
11
|
$badAnimals: [DairyAnimal]!,
|
12
|
+
$deepAnimals: [[DairyAnimal!]!]!,
|
12
13
|
) {
|
13
14
|
goodCheese: cheese(id: $goodInt) { source }
|
14
15
|
okCheese: cheese(id: $okInt) { source }
|
@@ -17,6 +18,11 @@ describe GraphQL::StaticValidation::VariableUsagesAreAllowed do
|
|
17
18
|
cheese(id: 1) {
|
18
19
|
similarCheeses(source: $goodAnimals)
|
19
20
|
other: similarCheeses(source: $badAnimals)
|
21
|
+
tooDeep: similarCheeses(source: $deepAnimals)
|
22
|
+
}
|
23
|
+
|
24
|
+
milk(id: 1) {
|
25
|
+
flavors(limit: $okInt)
|
20
26
|
}
|
21
27
|
}
|
22
28
|
')}
|
@@ -25,19 +31,23 @@ describe GraphQL::StaticValidation::VariableUsagesAreAllowed do
|
|
25
31
|
let(:errors) { validator.validate(document) }
|
26
32
|
|
27
33
|
it "finds variables used as arguments but don't match the argument's type" do
|
28
|
-
assert_equal(
|
34
|
+
assert_equal(4, errors.length)
|
29
35
|
expected = [
|
30
36
|
{
|
31
|
-
"message"=>"
|
32
|
-
"locations"=>[{"line"=>
|
37
|
+
"message"=>"Nullability mismatch on variable $badInt and argument id (Int / Int!)",
|
38
|
+
"locations"=>[{"line"=>13, "column"=>28}]
|
33
39
|
},
|
34
40
|
{
|
35
41
|
"message"=>"Type mismatch on variable $badStr and argument id (String! / Int!)",
|
36
|
-
"locations"=>[{"line"=>
|
42
|
+
"locations"=>[{"line"=>14, "column"=>28}]
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"message"=>"Nullability mismatch on variable $badAnimals and argument source ([DairyAnimal]! / [DairyAnimal!]!)",
|
46
|
+
"locations"=>[{"line"=>17, "column"=>31}]
|
37
47
|
},
|
38
48
|
{
|
39
|
-
"message"=>"
|
40
|
-
"locations"=>[{"line"=>
|
49
|
+
"message"=>"List dimension mismatch on variable $deepAnimals and argument source ([[DairyAnimal!]!]! / [DairyAnimal!]!)",
|
50
|
+
"locations"=>[{"line"=>18, "column"=>33}]
|
41
51
|
}
|
42
52
|
]
|
43
53
|
assert_equal(expected, errors)
|
data/spec/support/dairy_app.rb
CHANGED
@@ -140,8 +140,6 @@ ReplaceValuesInputType = GraphQL::InputObjectType.define do
|
|
140
140
|
input_field :values, !types[!types.Int]
|
141
141
|
end
|
142
142
|
|
143
|
-
p ReplaceValuesInputType.input_fields.inspect
|
144
|
-
|
145
143
|
MutationType = GraphQL::ObjectType.define do
|
146
144
|
name "Mutation"
|
147
145
|
description "The root for mutations in this schema"
|
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.6.
|
4
|
+
version: 0.6.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-08-
|
11
|
+
date: 2015-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parslet
|