graphql 0.18.15 → 0.19.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/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
|