graphql 0.18.15 → 0.19.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/define/assign_global_id_field.rb +1 -2
- data/lib/graphql/directive.rb +41 -7
- data/lib/graphql/directive/deprecated_directive.rb +11 -0
- data/lib/graphql/directive/include_directive.rb +2 -2
- data/lib/graphql/directive/skip_directive.rb +2 -2
- data/lib/graphql/input_object_type.rb +14 -0
- data/lib/graphql/internal_representation/node.rb +8 -4
- data/lib/graphql/introspection/arguments_field.rb +0 -1
- data/lib/graphql/introspection/directive_location_enum.rb +3 -2
- data/lib/graphql/introspection/directive_type.rb +12 -7
- data/lib/graphql/introspection/enum_value_type.rb +4 -2
- data/lib/graphql/introspection/enum_values_field.rb +0 -1
- data/lib/graphql/introspection/field_type.rb +8 -7
- data/lib/graphql/introspection/fields_field.rb +0 -1
- data/lib/graphql/introspection/input_fields_field.rb +0 -1
- data/lib/graphql/introspection/input_value_type.rb +8 -10
- data/lib/graphql/introspection/interfaces_field.rb +0 -1
- data/lib/graphql/introspection/of_type_field.rb +0 -1
- data/lib/graphql/introspection/possible_types_field.rb +0 -1
- data/lib/graphql/introspection/schema_type.rb +11 -9
- data/lib/graphql/introspection/type_kind_enum.rb +3 -3
- data/lib/graphql/introspection/type_type.rb +9 -6
- data/lib/graphql/language/generation.rb +4 -1
- data/lib/graphql/language/lexer.rb +353 -316
- data/lib/graphql/language/lexer.rl +8 -6
- data/lib/graphql/language/nodes.rb +12 -0
- data/lib/graphql/language/parser.rb +553 -501
- data/lib/graphql/language/parser.y +26 -16
- data/lib/graphql/language/parser_tests.rb +20 -1
- data/lib/graphql/list_type.rb +5 -1
- data/lib/graphql/non_null_type.rb +4 -0
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/relay.rb +1 -1
- data/lib/graphql/relay/global_id_resolve.rb +3 -5
- data/lib/graphql/relay/node.rb +34 -0
- data/lib/graphql/scalar_type.rb +1 -1
- data/lib/graphql/schema.rb +43 -15
- data/lib/graphql/schema/loader.rb +2 -2
- data/lib/graphql/schema/printer.rb +50 -8
- data/lib/graphql/schema/unique_within_type.rb +28 -0
- data/lib/graphql/schema/validation.rb +10 -3
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +9 -1
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -2
- data/lib/graphql/type_kinds.rb +12 -12
- data/lib/graphql/version.rb +1 -1
- data/readme.md +6 -5
- data/spec/graphql/argument_spec.rb +1 -1
- data/spec/graphql/execution_error_spec.rb +53 -0
- data/spec/graphql/introspection/directive_type_spec.rb +10 -0
- data/spec/graphql/introspection/input_value_type_spec.rb +23 -0
- data/spec/graphql/language/generation_spec.rb +4 -0
- data/spec/graphql/query/executor_spec.rb +2 -2
- data/spec/graphql/relay/mutation_spec.rb +1 -1
- data/spec/graphql/relay/node_spec.rb +87 -0
- data/spec/graphql/schema/catchall_middleware_spec.rb +1 -1
- data/spec/graphql/schema/loader_spec.rb +37 -4
- data/spec/graphql/schema/printer_spec.rb +30 -7
- data/spec/graphql/schema/unique_within_type_spec.rb +27 -0
- data/spec/graphql/schema/validation_spec.rb +7 -11
- data/spec/graphql/schema_spec.rb +32 -2
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +14 -15
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +10 -10
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +1 -5
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +3 -5
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -11
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +1 -4
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +10 -13
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +3 -5
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +2 -4
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -6
- data/spec/graphql/static_validation/rules/fragments_are_named_spec.rb +2 -4
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +7 -7
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -4
- data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +2 -4
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +3 -6
- data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +2 -4
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +12 -14
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +7 -9
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +20 -22
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +27 -23
- data/spec/spec_helper.rb +1 -0
- data/spec/support/dairy_app.rb +2 -2
- data/spec/support/star_wars_schema.rb +15 -18
- data/spec/support/static_validation_helpers.rb +27 -0
- metadata +25 -5
- data/lib/graphql/relay/global_node_identification.rb +0 -138
- data/spec/graphql/relay/global_node_identification_spec.rb +0 -153
@@ -0,0 +1,28 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Schema
|
3
|
+
module UniqueWithinType
|
4
|
+
DEFAULT_SEPARATOR = "-"
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# @param type_name [String]
|
9
|
+
# @param object_value [Any]
|
10
|
+
# @return [String] a unique, opaque ID generated as a function of the two inputs
|
11
|
+
def encode(type_name, object_value, separator: DEFAULT_SEPARATOR)
|
12
|
+
object_value_str = object_value.to_s
|
13
|
+
|
14
|
+
if type_name.include?(separator) || object_value_str.include?(separator)
|
15
|
+
raise "encode(#{type_name}, #{object_value_str}) contains reserved characters `#{separator}`"
|
16
|
+
end
|
17
|
+
|
18
|
+
Base64.strict_encode64([type_name, object_value_str].join(separator))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param node_id [String] A unique ID generated by {.encode}
|
22
|
+
# @return [Array<(String, String)>] The type name & value passed to {.encode}
|
23
|
+
def decode(node_id, separator: DEFAULT_SEPARATOR)
|
24
|
+
Base64.decode64(node_id).split(separator)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -100,9 +100,16 @@ module GraphQL
|
|
100
100
|
|
101
101
|
DEFAULT_VALUE_IS_VALID_FOR_TYPE = -> (type) {
|
102
102
|
if !type.default_value.nil?
|
103
|
-
coerced_value =
|
104
|
-
|
105
|
-
|
103
|
+
coerced_value = begin
|
104
|
+
type.type.coerce_result(type.default_value)
|
105
|
+
rescue => ex
|
106
|
+
ex
|
107
|
+
end
|
108
|
+
|
109
|
+
if coerced_value.nil? || coerced_value.is_a?(StandardError)
|
110
|
+
msg = "default value #{type.default_value.inspect} is not valid for type #{type.type}"
|
111
|
+
msg += " (#{coerced_value})" if coerced_value.is_a?(StandardError)
|
112
|
+
msg
|
106
113
|
end
|
107
114
|
end
|
108
115
|
}
|
@@ -76,7 +76,7 @@ module GraphQL
|
|
76
76
|
|
77
77
|
args = defs.map { |defn| reduce_list(defn.arguments)}.uniq
|
78
78
|
if args.length != 1
|
79
|
-
errors << message("Field '#{name}' has an argument conflict: #{args.map {|a|
|
79
|
+
errors << message("Field '#{name}' has an argument conflict: #{args.map {|a| print_arg(a) }.join(" or ")}?", defs.first, context: context)
|
80
80
|
end
|
81
81
|
|
82
82
|
@errors = errors
|
@@ -84,6 +84,14 @@ module GraphQL
|
|
84
84
|
|
85
85
|
private
|
86
86
|
|
87
|
+
def print_arg(arg)
|
88
|
+
case arg
|
89
|
+
when GraphQL::Language::Nodes::VariableIdentifier
|
90
|
+
"$#{arg.name}"
|
91
|
+
else
|
92
|
+
JSON.dump(arg)
|
93
|
+
end
|
94
|
+
end
|
87
95
|
# Turn AST tree into a hash
|
88
96
|
# can't look up args, the names just have to match
|
89
97
|
def reduce_list(args)
|
@@ -23,8 +23,10 @@ module GraphQL
|
|
23
23
|
context.visitor[GraphQL::Language::Nodes::Document].leave << -> (doc_node, parent) {
|
24
24
|
spreads_to_validate.each do |frag_spread|
|
25
25
|
fragment_child_name = context.fragments[frag_spread.node.name].type
|
26
|
-
fragment_child = context.schema.types
|
27
|
-
|
26
|
+
fragment_child = context.schema.types.fetch(fragment_child_name, nil) # Might be non-existent type name
|
27
|
+
if fragment_child
|
28
|
+
validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
|
29
|
+
end
|
28
30
|
end
|
29
31
|
}
|
30
32
|
end
|
@@ -32,6 +34,10 @@ module GraphQL
|
|
32
34
|
private
|
33
35
|
|
34
36
|
def validate_fragment_in_scope(parent_type, child_type, node, context, path)
|
37
|
+
if !child_type.kind.fields?
|
38
|
+
# It's not a valid fragment type, this error was handled someplace else
|
39
|
+
return
|
40
|
+
end
|
35
41
|
intersecting_types = get_possible_types(parent_type, context.schema) & get_possible_types(child_type, context.schema)
|
36
42
|
if intersecting_types.none?
|
37
43
|
name = node.respond_to?(:name) ? " #{node.name}" : ""
|
data/lib/graphql/type_kinds.rb
CHANGED
@@ -3,14 +3,15 @@ module GraphQL
|
|
3
3
|
module TypeKinds
|
4
4
|
# These objects are singletons, eg `GraphQL::TypeKinds::UNION`, `GraphQL::TypeKinds::SCALAR`.
|
5
5
|
class TypeKind
|
6
|
-
attr_reader :name
|
7
|
-
def initialize(name, resolves: false, fields: false, wraps: false, input: false)
|
6
|
+
attr_reader :name, :description
|
7
|
+
def initialize(name, resolves: false, fields: false, wraps: false, input: false, description: nil)
|
8
8
|
@name = name
|
9
9
|
@resolves = resolves
|
10
10
|
@fields = fields
|
11
11
|
@wraps = wraps
|
12
12
|
@input = input
|
13
13
|
@composite = fields? || resolves?
|
14
|
+
@description = description
|
14
15
|
end
|
15
16
|
|
16
17
|
# Does this TypeKind have multiple possible implementors?
|
@@ -27,19 +28,18 @@ module GraphQL
|
|
27
28
|
end
|
28
29
|
|
29
30
|
TYPE_KINDS = [
|
30
|
-
SCALAR = TypeKind.new("SCALAR", input: true),
|
31
|
-
OBJECT = TypeKind.new("OBJECT", fields: true),
|
32
|
-
INTERFACE = TypeKind.new("INTERFACE", resolves: true, fields: true),
|
33
|
-
UNION = TypeKind.new("UNION", resolves: true),
|
34
|
-
ENUM = TypeKind.new("ENUM", input: true),
|
35
|
-
INPUT_OBJECT = TypeKind.new("INPUT_OBJECT", input: true),
|
36
|
-
LIST = TypeKind.new("LIST", wraps: true),
|
37
|
-
NON_NULL = TypeKind.new("NON_NULL", wraps: true),
|
31
|
+
SCALAR = TypeKind.new("SCALAR", input: true, description: 'Indicates this type is a scalar.'),
|
32
|
+
OBJECT = TypeKind.new("OBJECT", fields: true, description: 'Indicates this type is an object. `fields` and `interfaces` are valid fields.'),
|
33
|
+
INTERFACE = TypeKind.new("INTERFACE", resolves: true, fields: true, description: 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.'),
|
34
|
+
UNION = TypeKind.new("UNION", resolves: true, description: 'Indicates this type is a union. `possibleTypes` is a valid field.'),
|
35
|
+
ENUM = TypeKind.new("ENUM", input: true, description: 'Indicates this type is an enum. `enumValues` is a valid field.'),
|
36
|
+
INPUT_OBJECT = TypeKind.new("INPUT_OBJECT", input: true, description: 'Indicates this type is an input object. `inputFields` is a valid field.'),
|
37
|
+
LIST = TypeKind.new("LIST", wraps: true, description: 'Indicates this type is a list. `ofType` is a valid field.'),
|
38
|
+
NON_NULL = TypeKind.new("NON_NULL", wraps: true, description: 'Indicates this type is a non-null. `ofType` is a valid field.'),
|
38
39
|
]
|
39
40
|
|
40
|
-
KIND_NAMES = TYPE_KINDS.map(&:name)
|
41
41
|
class TypeKind
|
42
|
-
|
42
|
+
TYPE_KINDS.map(&:name).each do |kind_name|
|
43
43
|
define_method("#{kind_name.downcase}?") do
|
44
44
|
self.name == kind_name
|
45
45
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -114,7 +114,7 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
114
114
|
|
115
115
|
## To Do
|
116
116
|
|
117
|
-
- StaticValidation improvements
|
117
|
+
- StaticValidation improvements ([in progress](https://github.com/rmosolgo/graphql-ruby/pull/268))
|
118
118
|
- Use catch-all type/field/argument definitions instead of terminating traversal
|
119
119
|
- Reduce ad-hoc traversals?
|
120
120
|
- Validators are order-dependent, is this a smell?
|
@@ -123,17 +123,16 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
123
123
|
- Add Rails-y argument validations, eg `less_than: 100`, `max_length: 255`, `one_of: [...]`
|
124
124
|
- Must be customizable
|
125
125
|
- Relay:
|
126
|
-
- `GlobalNodeIdentification.to_global_id` should receive the type name and _object_, not `id`. (Or, maintain the "`type_name, id` in, `type_name, id` out" pattern?)
|
127
126
|
- Reduce duplication in ArrayConnection / RelationConnection
|
128
127
|
- Improve API for creating edges (better RANGE_ADD support)
|
129
128
|
- If the new edge isn't a member of the connection's objects, raise a nice error
|
130
129
|
- Missing Enum value should raise a descriptive error, not "key not found"
|
131
130
|
- `args` should whitelist keys -- if you request a key that isn't defined for the field, it should 💥
|
132
|
-
- Fix middleware
|
131
|
+
- Fix middleware ([discussion](https://github.com/rmosolgo/graphql-ruby/issues/186))
|
133
132
|
- Handle out-of-bounds lookup, eg `graphql-batch`
|
134
133
|
- Handle non-serial execution, eg `@defer`
|
135
134
|
- Support non-instance-eval `.define`, eg `.define { |defn| ... }`
|
136
|
-
- First-class promise support
|
135
|
+
- First-class promise support ([discussion](https://github.com/rmosolgo/graphql-ruby/issues/274))
|
137
136
|
- like `graphql-batch` but more local
|
138
137
|
- support promises in connection resolves
|
139
138
|
- Add immutable transformation API to AST
|
@@ -141,10 +140,12 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
141
140
|
- Adding fields to selections (`__typename` can go anywhere, others are type-specific)
|
142
141
|
- Renaming fragments from local names to unique names
|
143
142
|
- Support AST subclasses? This would be hard, I think classes are used as hash keys in many places.
|
144
|
-
- Support object deep-copy (schema, type, field, argument)? To support multiple schemas based on the same types.
|
143
|
+
- Support object deep-copy (schema, type, field, argument)? To support multiple schemas based on the same types. ([discussion](https://github.com/rmosolgo/graphql-ruby/issues/269))
|
145
144
|
- Improve the website
|
146
145
|
- Feature the logo in the header
|
147
146
|
- Split `readme.md` into `index.md` (a homepage with code samples) and a technical readme (how to install, link to homepage)
|
148
147
|
- Move "Related projects" to a guide
|
149
148
|
- Revisit guides, maybe split them into smaller, more specific pages
|
150
149
|
- Put guide titles into the `<title />`
|
150
|
+
- Document encrypted & versioned cursors
|
151
|
+
- Eager load `Schema#types` after `.define { ... }`
|
@@ -15,7 +15,7 @@ describe GraphQL::Argument do
|
|
15
15
|
}
|
16
16
|
|
17
17
|
expected_error = %|Query is invalid: field "invalid" argument "invalid" default value ["123"] is not valid for type Float|
|
18
|
-
|
18
|
+
assert_includes err.message, expected_error
|
19
19
|
end
|
20
20
|
|
21
21
|
it "accepts proc type" do
|
@@ -118,4 +118,57 @@ describe GraphQL::ExecutionError do
|
|
118
118
|
assert_equal(expected_result, result)
|
119
119
|
end
|
120
120
|
end
|
121
|
+
|
122
|
+
describe "named query when returned from a field" do
|
123
|
+
let(:query_string) {%|
|
124
|
+
query MilkQuery {
|
125
|
+
dairy {
|
126
|
+
milks {
|
127
|
+
source
|
128
|
+
executionError
|
129
|
+
allDairy {
|
130
|
+
__typename
|
131
|
+
... on Milk {
|
132
|
+
origin
|
133
|
+
executionError
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|}
|
140
|
+
it "the error is inserted into the errors key and the rest of the query is fulfilled" do
|
141
|
+
expected_result = {
|
142
|
+
"data"=>{
|
143
|
+
"dairy" => {
|
144
|
+
"milks" => [
|
145
|
+
{
|
146
|
+
"source" => "COW",
|
147
|
+
"executionError" => nil,
|
148
|
+
"allDairy" => [
|
149
|
+
{ "__typename" => "Cheese" },
|
150
|
+
{ "__typename" => "Cheese" },
|
151
|
+
{ "__typename" => "Cheese" },
|
152
|
+
{ "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil }
|
153
|
+
]
|
154
|
+
}
|
155
|
+
]
|
156
|
+
}
|
157
|
+
},
|
158
|
+
"errors"=>[
|
159
|
+
{
|
160
|
+
"message"=>"There was an execution error",
|
161
|
+
"locations"=>[{"line"=>6, "column"=>11}],
|
162
|
+
"path"=>["dairy", "milks", 0, "executionError"]
|
163
|
+
},
|
164
|
+
{
|
165
|
+
"message"=>"There was an execution error",
|
166
|
+
"locations"=>[{"line"=>11, "column"=>15}],
|
167
|
+
"path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"]
|
168
|
+
}
|
169
|
+
]
|
170
|
+
}
|
171
|
+
assert_equal(expected_result, result)
|
172
|
+
end
|
173
|
+
end
|
121
174
|
end
|
@@ -42,6 +42,16 @@ describe GraphQL::Introspection::DirectiveType do
|
|
42
42
|
"onFragment" => true,
|
43
43
|
"onOperation" => false,
|
44
44
|
},
|
45
|
+
{
|
46
|
+
"name" => "deprecated",
|
47
|
+
"args" => [
|
48
|
+
{"name"=>"reason", "type"=>{"name"=>"String", "ofType"=>nil}}
|
49
|
+
],
|
50
|
+
"locations"=>["FIELD_DEFINITION", "ENUM_VALUE"],
|
51
|
+
"onField" => false,
|
52
|
+
"onFragment" => false,
|
53
|
+
"onOperation" => false,
|
54
|
+
},
|
45
55
|
]
|
46
56
|
}
|
47
57
|
}}
|
@@ -39,4 +39,27 @@ describe GraphQL::Introspection::InputValueType do
|
|
39
39
|
}}
|
40
40
|
assert_equal(expected, result)
|
41
41
|
end
|
42
|
+
|
43
|
+
let(:cheese_type) {
|
44
|
+
DummySchema.execute(%|
|
45
|
+
{
|
46
|
+
__type(name: "Cheese") {
|
47
|
+
fields {
|
48
|
+
name
|
49
|
+
args {
|
50
|
+
name
|
51
|
+
defaultValue
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|)
|
57
|
+
}
|
58
|
+
|
59
|
+
it "converts default values to GraphQL values" do
|
60
|
+
field = cheese_type['data']['__type']['fields'].detect { |f| f['name'] == 'similarCheese' }
|
61
|
+
arg = field['args'].detect { |a| a['name'] == 'source' }
|
62
|
+
|
63
|
+
assert_equal('["COW"]', arg['defaultValue'])
|
64
|
+
end
|
42
65
|
end
|
@@ -96,6 +96,10 @@ describe GraphQL::Language::Generation do
|
|
96
96
|
input AnnotatedInput @onInputObjectType {
|
97
97
|
annotatedField: Type @onField
|
98
98
|
}
|
99
|
+
|
100
|
+
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
101
|
+
|
102
|
+
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
99
103
|
schema
|
100
104
|
}
|
101
105
|
|
@@ -144,7 +144,7 @@ describe GraphQL::Query::Executor do
|
|
144
144
|
{
|
145
145
|
"message" => "BOOM",
|
146
146
|
"locations" => [ { "line" => 1, "column" => 28 } ],
|
147
|
-
"path" => ["
|
147
|
+
"path" => ["cow", "cantBeNullButRaisesExecutionError"]
|
148
148
|
}
|
149
149
|
]
|
150
150
|
}
|
@@ -169,7 +169,7 @@ describe GraphQL::Query::Executor do
|
|
169
169
|
{
|
170
170
|
"message"=>"Error was handled!",
|
171
171
|
"locations" => [{"line"=>1, "column"=>17}],
|
172
|
-
"path"=>["
|
172
|
+
"path"=>["error"]
|
173
173
|
}
|
174
174
|
]
|
175
175
|
}
|
@@ -33,7 +33,7 @@ describe GraphQL::Relay::Mutation do
|
|
33
33
|
"shipEdge" => {
|
34
34
|
"node" => {
|
35
35
|
"name" => "Bagel",
|
36
|
-
"id" =>
|
36
|
+
"id" => GraphQL::Schema::UniqueWithinType.encode("Ship", "9"),
|
37
37
|
},
|
38
38
|
},
|
39
39
|
"faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::Relay::Node do
|
4
|
+
describe ".field" do
|
5
|
+
describe "Custom global IDs" do
|
6
|
+
before do
|
7
|
+
# TODO: make the schema eager-load so we can remove this
|
8
|
+
# Ensure the schema is defined:
|
9
|
+
StarWarsSchema.types
|
10
|
+
|
11
|
+
@previous_id_from_object_proc = StarWarsSchema.id_from_object_proc
|
12
|
+
@previous_object_from_id_proc = StarWarsSchema.object_from_id_proc
|
13
|
+
|
14
|
+
StarWarsSchema.id_from_object = -> (obj, type_name, ctx) {
|
15
|
+
"#{type_name}/#{obj.id}"
|
16
|
+
}
|
17
|
+
|
18
|
+
StarWarsSchema.object_from_id = -> (global_id, ctx) {
|
19
|
+
type_name, id = global_id.split("/")
|
20
|
+
STAR_WARS_DATA[type_name][id]
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
after do
|
25
|
+
StarWarsSchema.id_from_object = @previous_id_from_object_proc
|
26
|
+
StarWarsSchema.object_from_id = @previous_object_from_id_proc
|
27
|
+
end
|
28
|
+
|
29
|
+
it "Deconstructs the ID by the custom proc" do
|
30
|
+
result = star_wars_query(%| { node(id: "Base/1") { ... on Base { name } } }|)
|
31
|
+
base_name = result["data"]["node"]["name"]
|
32
|
+
assert_equal "Yavin", base_name
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "generating IDs" do
|
36
|
+
it "Applies custom-defined ID generation" do
|
37
|
+
result = star_wars_query(%| { largestBase { id } }|)
|
38
|
+
generated_id = result["data"]["largestBase"]["id"]
|
39
|
+
assert_equal "Base/3", generated_id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "setting a description" do
|
45
|
+
it "allows you to set a description" do
|
46
|
+
node_field = GraphQL::Relay::Node.field
|
47
|
+
node_field.description = "Hello, World!"
|
48
|
+
assert_equal "Hello, World!", node_field.description
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it 'finds objects by id' do
|
54
|
+
id = GraphQL::Schema::UniqueWithinType.encode("Faction", "1")
|
55
|
+
result = star_wars_query(%|{
|
56
|
+
node(id: "#{id}") {
|
57
|
+
id,
|
58
|
+
... on Faction {
|
59
|
+
name
|
60
|
+
ships(first: 1) {
|
61
|
+
edges {
|
62
|
+
node {
|
63
|
+
name
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}|)
|
70
|
+
expected = {"data" => {
|
71
|
+
"node"=>{
|
72
|
+
"id"=>"RmFjdGlvbi0x",
|
73
|
+
"name"=>"Alliance to Restore the Republic",
|
74
|
+
"ships"=>{
|
75
|
+
"edges"=>[
|
76
|
+
{"node"=>{
|
77
|
+
"name" => "X-Wing"
|
78
|
+
}
|
79
|
+
}
|
80
|
+
]
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}}
|
84
|
+
assert_equal(expected, result)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -11,7 +11,7 @@ describe GraphQL::Schema::Loader do
|
|
11
11
|
choice_type = GraphQL::EnumType.define do
|
12
12
|
name "Choice"
|
13
13
|
|
14
|
-
value "FOO"
|
14
|
+
value "FOO", value: :foo
|
15
15
|
value "BAR"
|
16
16
|
end
|
17
17
|
|
@@ -30,7 +30,7 @@ describe GraphQL::Schema::Loader do
|
|
30
30
|
name "Varied"
|
31
31
|
input_field :id, types.ID
|
32
32
|
input_field :int, types.Int
|
33
|
-
input_field :bigint, big_int_type
|
33
|
+
input_field :bigint, big_int_type, default_value: 2**54
|
34
34
|
input_field :float, types.Float
|
35
35
|
input_field :bool, types.Boolean
|
36
36
|
input_field :enum, choice_type
|
@@ -43,6 +43,10 @@ describe GraphQL::Schema::Loader do
|
|
43
43
|
interfaces [node_type]
|
44
44
|
|
45
45
|
field :body, !types.String
|
46
|
+
|
47
|
+
field :fieldWithArg, types.Int do
|
48
|
+
argument :bigint, big_int_type, default_value: 2**54
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
media_type = GraphQL::InterfaceType.define do
|
@@ -85,7 +89,7 @@ describe GraphQL::Schema::Loader do
|
|
85
89
|
field :post do
|
86
90
|
type post_type
|
87
91
|
argument :id, !types.ID
|
88
|
-
argument :varied, variant_input_type, default_value: { id: "123", int: 234, float: 2.3, enum:
|
92
|
+
argument :varied, variant_input_type, default_value: { id: "123", int: 234, float: 2.3, enum: :foo, sub: [{ string: "str" }] }
|
89
93
|
end
|
90
94
|
|
91
95
|
field :content do
|
@@ -166,8 +170,37 @@ describe GraphQL::Schema::Loader do
|
|
166
170
|
end
|
167
171
|
end
|
168
172
|
|
173
|
+
let(:loaded_schema) { GraphQL::Schema::Loader.load(schema_json) }
|
174
|
+
|
169
175
|
it "returns the schema" do
|
170
|
-
assert_deep_equal(schema,
|
176
|
+
assert_deep_equal(schema, loaded_schema)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "can export the loaded schema" do
|
180
|
+
assert loaded_schema.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "sets correct default values on custom scalar arguments" do
|
184
|
+
type = loaded_schema.types["Comment"]
|
185
|
+
field = type.fields['fieldWithArg']
|
186
|
+
arg = field.arguments['bigint']
|
187
|
+
|
188
|
+
assert_equal((2**54).to_s, arg.default_value)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "sets correct default values on custom scalar input fields" do
|
192
|
+
type = loaded_schema.types["Varied"]
|
193
|
+
field = type.input_fields['bigint']
|
194
|
+
|
195
|
+
assert_equal((2**54).to_s, field.default_value)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "sets correct default values for complex field arguments" do
|
199
|
+
type = loaded_schema.types['Query']
|
200
|
+
field = type.fields['post']
|
201
|
+
arg = field.arguments['varied']
|
202
|
+
|
203
|
+
assert_equal arg.default_value, { 'id' => "123", 'bigint' => nil, 'bool' => nil, 'int' => 234, 'float' => 2.3, 'enum' => "FOO", 'sub' => [{ 'string' => "str" }] }
|
171
204
|
end
|
172
205
|
end
|
173
206
|
end
|