graphql 1.0.0 → 1.1.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.rb +10 -0
- data/lib/graphql/base_type.rb +8 -5
- data/lib/graphql/compatibility.rb +3 -0
- data/lib/graphql/compatibility/execution_specification.rb +414 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +117 -0
- data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +81 -0
- data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +78 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +239 -0
- data/lib/graphql/define/instance_definable.rb +53 -21
- data/lib/graphql/directive.rb +1 -1
- data/lib/graphql/enum_type.rb +31 -8
- data/lib/graphql/execution/directive_checks.rb +0 -6
- data/lib/graphql/input_object_type.rb +6 -4
- data/lib/graphql/introspection/arguments_field.rb +3 -1
- data/lib/graphql/introspection/enum_values_field.rb +10 -5
- data/lib/graphql/introspection/fields_field.rb +1 -1
- data/lib/graphql/introspection/input_fields_field.rb +2 -2
- data/lib/graphql/introspection/interfaces_field.rb +7 -1
- data/lib/graphql/introspection/possible_types_field.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/introspection/type_by_name_field.rb +4 -2
- data/lib/graphql/introspection/type_type.rb +7 -6
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/language/parser.y +1 -1
- data/lib/graphql/list_type.rb +3 -4
- data/lib/graphql/non_null_type.rb +4 -8
- data/lib/graphql/object_type.rb +5 -3
- data/lib/graphql/query.rb +48 -12
- data/lib/graphql/query/context.rb +7 -1
- data/lib/graphql/query/serial_execution/execution_context.rb +8 -3
- data/lib/graphql/query/serial_execution/field_resolution.rb +8 -5
- data/lib/graphql/query/serial_execution/operation_resolution.rb +2 -2
- data/lib/graphql/query/serial_execution/selection_resolution.rb +4 -21
- data/lib/graphql/query/serial_execution/value_resolution.rb +59 -99
- data/lib/graphql/query/variables.rb +7 -2
- data/lib/graphql/scalar_type.rb +1 -1
- data/lib/graphql/schema.rb +49 -18
- data/lib/graphql/schema/build_from_definition.rb +248 -0
- data/lib/graphql/schema/instrumented_field_map.rb +23 -0
- data/lib/graphql/schema/loader.rb +4 -11
- data/lib/graphql/schema/possible_types.rb +4 -2
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +4 -4
- data/lib/graphql/schema/type_map.rb +1 -1
- data/lib/graphql/schema/validation.rb +4 -0
- data/lib/graphql/schema/warden.rb +114 -0
- data/lib/graphql/static_validation/literal_validator.rb +10 -7
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -3
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -14
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -4
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +2 -1
- data/lib/graphql/static_validation/validation_context.rb +7 -1
- data/lib/graphql/union_type.rb +6 -3
- data/lib/graphql/unresolved_type_error.rb +1 -2
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -5
- data/spec/graphql/compatibility/execution_specification_spec.rb +3 -0
- data/spec/graphql/compatibility/query_parser_specification_spec.rb +5 -0
- data/spec/graphql/compatibility/schema_parser_specification_spec.rb +5 -0
- data/spec/graphql/define/instance_definable_spec.rb +20 -0
- data/spec/graphql/directive_spec.rb +11 -0
- data/spec/graphql/enum_type_spec.rb +20 -1
- data/spec/graphql/input_object_type_spec.rb +9 -9
- data/spec/graphql/introspection/directive_type_spec.rb +4 -4
- data/spec/graphql/introspection/input_value_type_spec.rb +6 -6
- data/spec/graphql/introspection/type_type_spec.rb +28 -26
- data/spec/graphql/language/parser_spec.rb +27 -17
- data/spec/graphql/list_type_spec.rb +2 -2
- data/spec/graphql/query/variables_spec.rb +1 -0
- data/spec/graphql/scalar_type_spec.rb +3 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +693 -0
- data/spec/graphql/schema/type_expression_spec.rb +3 -3
- data/spec/graphql/schema/validation_spec.rb +7 -3
- data/spec/graphql/schema/warden_spec.rb +510 -0
- data/spec/graphql/schema_spec.rb +129 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
- data/spec/graphql/static_validation/type_stack_spec.rb +3 -3
- data/spec/spec_helper.rb +27 -1
- data/spec/support/dairy_app.rb +8 -5
- metadata +21 -3
- data/lib/graphql/language/parser_tests.rb +0 -809
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
def initialize(schema)
|
15
15
|
@object_types = schema.types.values.select { |type| type.kind.object? }
|
16
16
|
|
17
|
-
@
|
17
|
+
@interface_implementers = Hash.new do |hash, key|
|
18
18
|
hash[key] = @object_types.select { |type| type.interfaces.include?(key) }.sort_by(&:name)
|
19
19
|
end
|
20
20
|
end
|
@@ -24,7 +24,9 @@ module GraphQL
|
|
24
24
|
when GraphQL::UnionType
|
25
25
|
type_defn.possible_types
|
26
26
|
when GraphQL::InterfaceType
|
27
|
-
@
|
27
|
+
@interface_implementers[type_defn]
|
28
|
+
when GraphQL::ObjectType
|
29
|
+
[type_defn]
|
28
30
|
else
|
29
31
|
raise "#{type_defn} doesn't have possible types"
|
30
32
|
end
|
@@ -162,7 +162,7 @@ module GraphQL
|
|
162
162
|
include ArgsPrinter
|
163
163
|
include DescriptionPrinter
|
164
164
|
def print_fields(type)
|
165
|
-
type.all_fields.map.with_index{ |field, i|
|
165
|
+
type.all_fields.map.with_index { |field, i|
|
166
166
|
"#{print_description(field, ' ', i == 0)}"\
|
167
167
|
" #{field.name}#{print_args(field, ' ')}: #{field.type}#{print_deprecated(field)}"
|
168
168
|
}.join("\n")
|
@@ -1,17 +1,17 @@
|
|
1
1
|
module GraphQL
|
2
2
|
class Schema
|
3
3
|
module TypeExpression
|
4
|
-
def self.build_type(
|
4
|
+
def self.build_type(types, ast_node)
|
5
5
|
case ast_node
|
6
6
|
when GraphQL::Language::Nodes::TypeName
|
7
7
|
type_name = ast_node.name
|
8
|
-
|
8
|
+
types[type_name]
|
9
9
|
when GraphQL::Language::Nodes::NonNullType
|
10
10
|
ast_inner_type = ast_node.of_type
|
11
|
-
build_type(
|
11
|
+
build_type(types, ast_inner_type).to_non_null_type
|
12
12
|
when GraphQL::Language::Nodes::ListType
|
13
13
|
ast_inner_type = ast_node.of_type
|
14
|
-
build_type(
|
14
|
+
build_type(types, ast_inner_type).to_list_type
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
# If you want a type, but want to handle the undefined case, use {#fetch}.
|
9
9
|
class TypeMap
|
10
10
|
extend Forwardable
|
11
|
-
def_delegators :@storage, :key?, :keys, :values, :to_h, :fetch
|
11
|
+
def_delegators :@storage, :key?, :keys, :values, :to_h, :fetch, :each, :each_value
|
12
12
|
|
13
13
|
def initialize
|
14
14
|
@storage = {}
|
@@ -99,6 +99,10 @@ module GraphQL
|
|
99
99
|
ARGUMENTS_ARE_VALID = Rules.assert_named_items_are_valid("argument", ->(type) { type.arguments.values })
|
100
100
|
|
101
101
|
DEFAULT_VALUE_IS_VALID_FOR_TYPE = ->(type) {
|
102
|
+
if !type.default_value.nil? && type.type.is_a?(NonNullType)
|
103
|
+
return %Q(Variable #{type.name} of type "#{type.type}" is required and will not use the default value. Perhaps you meant to use type "#{type.type.of_type}".)
|
104
|
+
end
|
105
|
+
|
102
106
|
if !type.default_value.nil?
|
103
107
|
coerced_value = begin
|
104
108
|
type.type.coerce_result(type.default_value)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Schema
|
3
|
+
# Restrict access to a {GraphQL::Schema} with a user-defined mask.
|
4
|
+
#
|
5
|
+
# The mask is object that responds to `#visible?(schema_member)`.
|
6
|
+
#
|
7
|
+
# When validating and executing a query, all access to schema members
|
8
|
+
# should go through a warden. If you access the schema directly,
|
9
|
+
# you may show a client something that it shouldn't be allowed to see.
|
10
|
+
#
|
11
|
+
# Masks can be provided in {Schema#execute} (or {Query#initialize}) with the `mask:` keyword.
|
12
|
+
#
|
13
|
+
# @example Hidding private fields
|
14
|
+
# private_members = -> (member) { member.metadata[:private] }
|
15
|
+
# result = Schema.execute(query_string, except: private_members)
|
16
|
+
#
|
17
|
+
# @example Custom mask implementation
|
18
|
+
# # It must respond to `#call(member)`.
|
19
|
+
# class MissingRequiredFlags
|
20
|
+
# def initialize(user)
|
21
|
+
# @user = user
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # Return `false` if any required flags are missing
|
25
|
+
# def call(member)
|
26
|
+
# member.metadata[:required_flags].any? do |flag|
|
27
|
+
# !@user.has_flag?(flag)
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # Then, use the custom filter in query:
|
33
|
+
# missing_required_flags = MissingRequiredFlags.new(current_user)
|
34
|
+
#
|
35
|
+
# # This query can only access members which match the user's flags
|
36
|
+
# result = Schema.execute(query_string, except: missing_required_flags)
|
37
|
+
#
|
38
|
+
class Warden
|
39
|
+
# @param schema [GraphQL::Schema]
|
40
|
+
# @param mask [<#call(member)>] Objects are hidden when `.call(member)` returns true
|
41
|
+
def initialize(schema, mask)
|
42
|
+
@mask = mask
|
43
|
+
@schema = schema
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Array<GraphQL::BaseType>] Visible types in the schema
|
47
|
+
def types
|
48
|
+
@schema.types.each_value.select { |t| visible?(t) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
|
52
|
+
def get_type(type_name)
|
53
|
+
type_defn = @schema.types.fetch(type_name, nil)
|
54
|
+
if type_defn && visible?(type_defn)
|
55
|
+
type_defn
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists
|
62
|
+
def get_field(parent_type, field_name)
|
63
|
+
field_defn = @schema.get_field(parent_type, field_name)
|
64
|
+
if field_defn && visible_field?(field_defn)
|
65
|
+
field_defn
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
|
72
|
+
def possible_types(type_defn)
|
73
|
+
@schema.possible_types(type_defn).select { |t| visible?(t) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType]
|
77
|
+
# @return [Array<GraphQL::Field>] Fields on `type_defn`
|
78
|
+
def fields(type_defn)
|
79
|
+
type_defn.all_fields.select { |f| visible_field?(f) }
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
|
83
|
+
# @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
|
84
|
+
def arguments(argument_owner)
|
85
|
+
argument_owner.arguments.each_value.select { |a| visible_field?(a) }
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Array<GraphQL::EnumType::EnumValue>] Visible members of `enum_defn`
|
89
|
+
def enum_values(enum_defn)
|
90
|
+
enum_defn.values.each_value.select { |enum_value_defn| visible?(enum_value_defn) }
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Array<GraphQL::InterfaceType>] Visible interfaces implemented by `obj_type`
|
94
|
+
def interfaces(obj_type)
|
95
|
+
obj_type.interfaces.select { |t| visible?(t) }
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Array<GraphQL::Field>] Visible input fields on `input_obj_type`
|
99
|
+
def input_fields(input_obj_type)
|
100
|
+
input_obj_type.arguments.each_value.select { |f| visible_field?(f) }
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def visible_field?(field_defn)
|
106
|
+
visible?(field_defn) && visible?(field_defn.type.unwrap)
|
107
|
+
end
|
108
|
+
|
109
|
+
def visible?(member)
|
110
|
+
!@mask.call(member)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -2,6 +2,10 @@ module GraphQL
|
|
2
2
|
module StaticValidation
|
3
3
|
# Test whether `ast_value` is a valid input for `type`
|
4
4
|
class LiteralValidator
|
5
|
+
def initialize(warden:)
|
6
|
+
@warden = warden
|
7
|
+
end
|
8
|
+
|
5
9
|
def validate(ast_value, type)
|
6
10
|
if type.kind.non_null?
|
7
11
|
(!ast_value.nil?) && validate(ast_value, type.of_type)
|
@@ -9,9 +13,9 @@ module GraphQL
|
|
9
13
|
item_type = type.of_type
|
10
14
|
ensure_array(ast_value).all? { |val| validate(val, item_type) }
|
11
15
|
elsif type.kind.scalar? && !ast_value.is_a?(GraphQL::Language::Nodes::AbstractNode) && !ast_value.is_a?(Array)
|
12
|
-
type.valid_input?(ast_value)
|
16
|
+
type.valid_input?(ast_value, @warden)
|
13
17
|
elsif type.kind.enum? && ast_value.is_a?(GraphQL::Language::Nodes::Enum)
|
14
|
-
type.valid_input?(ast_value.name)
|
18
|
+
type.valid_input?(ast_value.name, @warden)
|
15
19
|
elsif type.kind.input_object? && ast_value.is_a?(GraphQL::Language::Nodes::InputObject)
|
16
20
|
required_input_fields_are_present(type, ast_value) &&
|
17
21
|
present_input_field_values_are_valid(type, ast_value)
|
@@ -27,8 +31,7 @@ module GraphQL
|
|
27
31
|
|
28
32
|
|
29
33
|
def required_input_fields_are_present(type, ast_node)
|
30
|
-
required_field_names =
|
31
|
-
.values
|
34
|
+
required_field_names = @warden.input_fields(type)
|
32
35
|
.select { |f| f.type.kind.non_null? }
|
33
36
|
.map(&:name)
|
34
37
|
present_field_names = ast_node.arguments.map(&:name)
|
@@ -37,10 +40,10 @@ module GraphQL
|
|
37
40
|
end
|
38
41
|
|
39
42
|
def present_input_field_values_are_valid(type, ast_node)
|
40
|
-
|
43
|
+
field_map = @warden.input_fields(type).reduce({}) { |m, f| m[f.name] = f; m}
|
41
44
|
ast_node.arguments.all? do |value|
|
42
|
-
field =
|
43
|
-
field
|
45
|
+
field = field_map[value.name]
|
46
|
+
field && validate(value.value, field.type)
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
@@ -3,11 +3,9 @@ module GraphQL
|
|
3
3
|
class ArgumentLiteralsAreCompatible < GraphQL::StaticValidation::ArgumentsValidator
|
4
4
|
def validate_node(parent, node, defn, context)
|
5
5
|
return if node.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
6
|
-
validator = GraphQL::StaticValidation::LiteralValidator.new
|
7
6
|
arg_defn = defn.arguments[node.name]
|
8
7
|
return unless arg_defn
|
9
|
-
|
10
|
-
if !valid
|
8
|
+
if !context.valid_literal?(node.value, arg_defn.type)
|
11
9
|
kind_of_node = node_type(parent)
|
12
10
|
error_arg_name = parent_name(parent, defn)
|
13
11
|
context.errors << message("Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value. Expected type '#{arg_defn.type}'.", parent, context: context)
|
@@ -2,7 +2,7 @@ module GraphQL
|
|
2
2
|
module StaticValidation
|
3
3
|
class ArgumentsAreDefined < GraphQL::StaticValidation::ArgumentsValidator
|
4
4
|
def validate_node(parent, node, defn, context)
|
5
|
-
argument_defn = defn.
|
5
|
+
argument_defn = context.warden.arguments(defn).find { |arg| arg.name == node.name }
|
6
6
|
if argument_defn.nil?
|
7
7
|
kind_of_node = node_type(parent)
|
8
8
|
error_arg_name = parent_name(parent, defn)
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
21
21
|
return GraphQL::Language::Visitor::SKIP
|
22
22
|
end
|
23
23
|
|
24
|
-
field = context.
|
24
|
+
field = context.warden.get_field(parent_type, ast_field.name)
|
25
25
|
if field.nil?
|
26
26
|
context.errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", ast_field, context: context)
|
27
27
|
return GraphQL::Language::Visitor::SKIP
|
@@ -23,7 +23,8 @@ 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.name
|
26
|
-
fragment_child = context.
|
26
|
+
fragment_child = context.warden.get_type(fragment_child_name)
|
27
|
+
# Might be non-existent type name
|
27
28
|
if fragment_child
|
28
29
|
validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
|
29
30
|
end
|
@@ -38,25 +39,13 @@ module GraphQL
|
|
38
39
|
# It's not a valid fragment type, this error was handled someplace else
|
39
40
|
return
|
40
41
|
end
|
41
|
-
intersecting_types =
|
42
|
+
intersecting_types = context.warden.possible_types(parent_type.unwrap) & context.warden.possible_types(child_type.unwrap)
|
42
43
|
if intersecting_types.none?
|
43
44
|
name = node.respond_to?(:name) ? " #{node.name}" : ""
|
44
45
|
context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node, path: path)
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
def get_possible_types(type, schema)
|
49
|
-
if type.kind.wraps?
|
50
|
-
get_possible_types(type.of_type, schema)
|
51
|
-
elsif type.kind.object?
|
52
|
-
[type]
|
53
|
-
elsif type.kind.resolves?
|
54
|
-
schema.possible_types(type)
|
55
|
-
else
|
56
|
-
[]
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
49
|
class FragmentSpread
|
61
50
|
attr_reader :node, :parent_type, :path
|
62
51
|
def initialize(node:, parent_type:, path:)
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
19
19
|
def validate_type_exists(node, context)
|
20
20
|
return unless node.type
|
21
21
|
type_name = node.type.name
|
22
|
-
type = context.
|
22
|
+
type = context.warden.get_type(type_name)
|
23
23
|
if type.nil?
|
24
24
|
context.errors << message("No such type #{type_name}, so it can't be a fragment condition", node, context: context)
|
25
25
|
GraphQL::Language::Visitor::SKIP
|
@@ -24,7 +24,7 @@ module GraphQL
|
|
24
24
|
# Inline fragment on the same type
|
25
25
|
else
|
26
26
|
type_name = node_type.to_query_string
|
27
|
-
type_def = context.
|
27
|
+
type_def = context.warden.get_type(type_name)
|
28
28
|
if type_def.nil? || !type_def.kind.composite?
|
29
29
|
context.errors << message("Invalid fragment on type #{type_name} (must be Union, Interface or Object)", node, context: context)
|
30
30
|
GraphQL::Language::Visitor::SKIP
|
@@ -4,21 +4,20 @@ module GraphQL
|
|
4
4
|
include GraphQL::StaticValidation::Message::MessageHelper
|
5
5
|
|
6
6
|
def validate(context)
|
7
|
-
literal_validator = GraphQL::StaticValidation::LiteralValidator.new
|
8
7
|
context.visitor[GraphQL::Language::Nodes::VariableDefinition] << ->(node, parent) {
|
9
8
|
if !node.default_value.nil?
|
10
|
-
validate_default_value(node,
|
9
|
+
validate_default_value(node, context)
|
11
10
|
end
|
12
11
|
}
|
13
12
|
end
|
14
13
|
|
15
|
-
def validate_default_value(node,
|
14
|
+
def validate_default_value(node, context)
|
16
15
|
value = node.default_value
|
17
16
|
if node.type.is_a?(GraphQL::Language::Nodes::NonNullType)
|
18
17
|
context.errors << message("Non-null variable $#{node.name} can't have a default value", node, context: context)
|
19
18
|
else
|
20
19
|
type = context.schema.type_from_ast(node.type)
|
21
|
-
if !
|
20
|
+
if !context.valid_literal?(value, type)
|
22
21
|
context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node, context: context)
|
23
22
|
end
|
24
23
|
end
|
@@ -13,7 +13,8 @@ module GraphQL
|
|
13
13
|
|
14
14
|
def validate_is_input_type(node, context)
|
15
15
|
type_name = get_type_name(node.type)
|
16
|
-
type = context.
|
16
|
+
type = context.warden.get_type(type_name)
|
17
|
+
|
17
18
|
if type.nil?
|
18
19
|
context.errors << message("#{type_name} isn't a defined input type (on $#{node.name})", node, context: context)
|
19
20
|
elsif !type.kind.input?
|
@@ -11,13 +11,14 @@ module GraphQL
|
|
11
11
|
# It also provides limited access to the {TypeStack} instance,
|
12
12
|
# which tracks state as you climb in and out of different fields.
|
13
13
|
class ValidationContext
|
14
|
-
attr_reader :query, :schema, :document, :errors, :visitor, :fragments, :operations
|
14
|
+
attr_reader :query, :schema, :document, :errors, :visitor, :fragments, :operations, :warden
|
15
15
|
def initialize(query)
|
16
16
|
@query = query
|
17
17
|
@schema = query.schema
|
18
18
|
@document = query.document
|
19
19
|
@fragments = {}
|
20
20
|
@operations = {}
|
21
|
+
@warden = query.warden
|
21
22
|
|
22
23
|
document.definitions.each do |definition|
|
23
24
|
case definition
|
@@ -69,6 +70,11 @@ module GraphQL
|
|
69
70
|
@type_stack.argument_definitions[-2]
|
70
71
|
end
|
71
72
|
|
73
|
+
def valid_literal?(ast_value, type)
|
74
|
+
@literal_validator ||= LiteralValidator.new(warden: @warden)
|
75
|
+
@literal_validator.validate(ast_value, type)
|
76
|
+
end
|
77
|
+
|
72
78
|
# Don't try to validate dynamic fields
|
73
79
|
# since they aren't defined by the type system
|
74
80
|
def skip_field?(field_name)
|
data/lib/graphql/union_type.rb
CHANGED
@@ -41,9 +41,12 @@ module GraphQL
|
|
41
41
|
def possible_types
|
42
42
|
@clean_possible_types ||= begin
|
43
43
|
ensure_defined
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
|
45
|
+
if @dirty_possible_types.respond_to?(:map)
|
46
|
+
@dirty_possible_types.map { |type| GraphQL::BaseType.resolve_related_type(type) }
|
47
|
+
else
|
48
|
+
@dirty_possible_types
|
49
|
+
end
|
47
50
|
end
|
48
51
|
end
|
49
52
|
end
|
@@ -2,8 +2,7 @@ module GraphQL
|
|
2
2
|
# Error raised when the value provided for a field can't be resolved to one of the possible types
|
3
3
|
# for the field.
|
4
4
|
class UnresolvedTypeError < GraphQL::Error
|
5
|
-
def initialize(field_name,
|
6
|
-
possible_types = field_type.kind.resolves? ? schema.possible_types(field_type) : [field_type]
|
5
|
+
def initialize(field_name, field_type, parent_type, received_type, possible_types)
|
7
6
|
message = %|The value from "#{field_name}" on "#{parent_type}" could not be resolved to "#{field_type}". (Received: #{received_type.inspect}, Expected: [#{possible_types.map(&:inspect).join(", ")}])|
|
8
7
|
super(message)
|
9
8
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -50,13 +50,10 @@ See "Getting Started" on the [website](https://rmosolgo.github.io/graphql-ruby/)
|
|
50
50
|
- Validators are order-dependent, is this a smell?
|
51
51
|
- Tests for interference between validators are poor
|
52
52
|
- Maybe this is a candidate for a rewrite?
|
53
|
-
- Add Rails-y argument validations, eg `less_than: 100`, `max_length: 255`, `one_of: [...]`
|
54
|
-
- Must be customizable
|
55
53
|
- Relay:
|
56
54
|
- Reduce duplication in ArrayConnection / RelationConnection
|
57
55
|
- Improve API for creating edges (better RANGE_ADD support)
|
58
56
|
- If the new edge isn't a member of the connection's objects, raise a nice error
|
59
|
-
- Missing Enum value should raise a descriptive error, not "key not found"
|
60
57
|
- `args` should whitelist keys -- if you request a key that isn't defined for the field, it should 💥
|
61
58
|
- Fix middleware ([discussion](https://github.com/rmosolgo/graphql-ruby/issues/186))
|
62
59
|
- Handle out-of-bounds lookup, eg `graphql-batch`
|
@@ -69,6 +66,5 @@ See "Getting Started" on the [website](https://rmosolgo.github.io/graphql-ruby/)
|
|
69
66
|
- Support working with AST as data
|
70
67
|
- Adding fields to selections (`__typename` can go anywhere, others are type-specific)
|
71
68
|
- Renaming fragments from local names to unique names
|
72
|
-
- Support AST subclasses? This would be hard, I think classes are used as hash keys in many places.
|
73
|
-
- 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))
|
74
69
|
- Document encrypted & versioned cursors
|
70
|
+
- Make it faster
|