graphql 1.12.10 → 1.13.4
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/generators/graphql/core.rb +3 -1
- data/lib/generators/graphql/install_generator.rb +9 -2
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +28 -1
- data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
- data/lib/graphql/analysis/ast/visitor.rb +4 -4
- data/lib/graphql/backtrace/table.rb +15 -3
- data/lib/graphql/backtrace/tracer.rb +7 -4
- data/lib/graphql/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/null_dataloader.rb +1 -0
- data/lib/graphql/dataloader/source.rb +50 -2
- data/lib/graphql/dataloader.rb +110 -41
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- data/lib/graphql/directive.rb +0 -4
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/execution/errors.rb +1 -0
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -4
- data/lib/graphql/execution/interpreter/resolve.rb +6 -2
- data/lib/graphql/execution/interpreter/runtime.rb +513 -213
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +4 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/enum_value_type.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/input_value_type.rb +10 -4
- data/lib/graphql/introspection/schema_type.rb +3 -3
- data/lib/graphql/introspection/type_type.rb +10 -10
- data/lib/graphql/language/block_string.rb +2 -6
- data/lib/graphql/language/document_from_schema_definition.rb +10 -4
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/nodes.rb +13 -3
- data/lib/graphql/language/parser.rb +442 -434
- data/lib/graphql/language/parser.y +5 -4
- data/lib/graphql/language/printer.rb +6 -1
- data/lib/graphql/language/sanitized_printer.rb +5 -5
- data/lib/graphql/language/token.rb +0 -4
- data/lib/graphql/name_validator.rb +0 -4
- data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/connections.rb +40 -16
- data/lib/graphql/pagination/relation_connection.rb +57 -27
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +15 -2
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/null_context.rb +12 -7
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/query.rb +5 -1
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/relay/global_id_resolve.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
- data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
- data/lib/graphql/rubocop.rb +4 -0
- data/lib/graphql/schema/addition.rb +247 -0
- data/lib/graphql/schema/argument.rb +103 -45
- data/lib/graphql/schema/build_from_definition.rb +13 -7
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive/transform.rb +14 -2
- data/lib/graphql/schema/directive.rb +7 -3
- data/lib/graphql/schema/enum.rb +70 -11
- data/lib/graphql/schema/enum_value.rb +6 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +243 -81
- data/lib/graphql/schema/field_extension.rb +89 -2
- data/lib/graphql/schema/find_inherited_value.rb +1 -0
- data/lib/graphql/schema/finder.rb +5 -5
- data/lib/graphql/schema/input_object.rb +39 -29
- data/lib/graphql/schema/interface.rb +11 -20
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +3 -1
- data/lib/graphql/schema/member/accepts_definition.rb +15 -3
- data/lib/graphql/schema/member/build_type.rb +1 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +145 -57
- data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +76 -18
- data/lib/graphql/schema/member/has_interfaces.rb +90 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/non_null.rb +7 -1
- data/lib/graphql/schema/object.rb +10 -75
- data/lib/graphql/schema/printer.rb +12 -17
- data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +75 -65
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +36 -8
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +1 -1
- data/lib/graphql/schema/type_membership.rb +18 -4
- data/lib/graphql/schema/union.rb +8 -1
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -5
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema/validator.rb +33 -25
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +162 -227
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +8 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/error.rb +3 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
- data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
- data/lib/graphql/static_validation/validation_context.rb +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +36 -6
- data/lib/graphql/subscriptions/event.rb +68 -31
- data/lib/graphql/subscriptions/serialize.rb +23 -3
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/types/big_int.rb +5 -1
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- data/lib/graphql/types/relay/has_node_field.rb +2 -2
- data/lib/graphql/types/relay/has_nodes_field.rb +2 -2
- data/lib/graphql/types/relay/node_field.rb +15 -4
- data/lib/graphql/types/relay/nodes_field.rb +14 -4
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -28
- data/readme.md +1 -4
- metadata +17 -21
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "base_cop"
|
|
3
|
+
|
|
4
|
+
module GraphQL
|
|
5
|
+
module Rubocop
|
|
6
|
+
module GraphQL
|
|
7
|
+
# Identify (and auto-correct) any field configuration which duplicates
|
|
8
|
+
# the default `null: true` property.
|
|
9
|
+
#
|
|
10
|
+
# `null: true` is default because nullable fields can always be converted
|
|
11
|
+
# to non-null fields (`null: false`) without a breaking change. (The opposite change, from `null: false`
|
|
12
|
+
# to `null: true`, change.)
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# # Both of these define `name: String` in GraphQL:
|
|
16
|
+
#
|
|
17
|
+
# # bad
|
|
18
|
+
# field :name, String, null: true
|
|
19
|
+
#
|
|
20
|
+
# # good
|
|
21
|
+
# field :name, String
|
|
22
|
+
#
|
|
23
|
+
class DefaultNullTrue < BaseCop
|
|
24
|
+
MSG = "`null: true` is the default and can be removed."
|
|
25
|
+
|
|
26
|
+
def_node_matcher :field_config_with_null_true?, <<-Pattern
|
|
27
|
+
(
|
|
28
|
+
send nil? :field ... (hash $(pair (sym :null) (true)) ...)
|
|
29
|
+
)
|
|
30
|
+
Pattern
|
|
31
|
+
|
|
32
|
+
def on_send(node)
|
|
33
|
+
field_config_with_null_true?(node) do |null_config|
|
|
34
|
+
add_offense(null_config) do |corrector|
|
|
35
|
+
cleaned_node_source = source_without_keyword_argument(node, null_config)
|
|
36
|
+
corrector.replace(node.source_range, cleaned_node_source)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "./base_cop"
|
|
3
|
+
|
|
4
|
+
module GraphQL
|
|
5
|
+
module Rubocop
|
|
6
|
+
module GraphQL
|
|
7
|
+
# Identify (and auto-correct) any argument configuration which duplicates
|
|
8
|
+
# the default `required: true` property.
|
|
9
|
+
#
|
|
10
|
+
# `required: true` is default because required arguments can always be converted
|
|
11
|
+
# to optional arguments (`required: false`) without a breaking change. (The opposite change, from `required: false`
|
|
12
|
+
# to `required: true`, change.)
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# # Both of these define `id: ID!` in GraphQL:
|
|
16
|
+
#
|
|
17
|
+
# # bad
|
|
18
|
+
# argument :id, ID, required: true
|
|
19
|
+
#
|
|
20
|
+
# # good
|
|
21
|
+
# argument :id, ID
|
|
22
|
+
#
|
|
23
|
+
class DefaultRequiredTrue < BaseCop
|
|
24
|
+
MSG = "`required: true` is the default and can be removed."
|
|
25
|
+
|
|
26
|
+
def_node_matcher :argument_config_with_required_true?, <<-Pattern
|
|
27
|
+
(
|
|
28
|
+
send nil? :argument ... (hash <$(pair (sym :required) (true)) ...>)
|
|
29
|
+
)
|
|
30
|
+
Pattern
|
|
31
|
+
|
|
32
|
+
def on_send(node)
|
|
33
|
+
argument_config_with_required_true?(node) do |required_config|
|
|
34
|
+
add_offense(required_config) do |corrector|
|
|
35
|
+
cleaned_node_source = source_without_keyword_argument(node, required_config)
|
|
36
|
+
corrector.replace(node, cleaned_node_source)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
class Schema
|
|
5
|
+
class Addition
|
|
6
|
+
attr_reader :directives, :possible_types, :types, :union_memberships, :references, :arguments_with_default_values
|
|
7
|
+
|
|
8
|
+
def initialize(schema:, own_types:, new_types:)
|
|
9
|
+
@schema = schema
|
|
10
|
+
@own_types = own_types
|
|
11
|
+
@directives = Set.new
|
|
12
|
+
@possible_types = {}
|
|
13
|
+
@types = {}
|
|
14
|
+
@union_memberships = {}
|
|
15
|
+
@references = Hash.new { |h, k| h[k] = [] }
|
|
16
|
+
@arguments_with_default_values = []
|
|
17
|
+
add_type_and_traverse(new_types)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def references_to(thing, from:)
|
|
23
|
+
@references[thing] << from
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get_type(name)
|
|
27
|
+
local_type = @types[name]
|
|
28
|
+
# This isn't really sophisticated, but
|
|
29
|
+
# I think it's good enough to support the current usage of LateBoundTypes
|
|
30
|
+
if local_type.is_a?(Array)
|
|
31
|
+
local_type = local_type.first
|
|
32
|
+
end
|
|
33
|
+
local_type || @schema.get_type(name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Lookup using `own_types` here because it's ok to override
|
|
37
|
+
# inherited types by name
|
|
38
|
+
def get_local_type(name)
|
|
39
|
+
@types[name] || @own_types[name]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_directives_from(owner)
|
|
43
|
+
dirs = owner.directives.map(&:class)
|
|
44
|
+
@directives.merge(dirs)
|
|
45
|
+
add_type_and_traverse(dirs)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_type_and_traverse(new_types)
|
|
49
|
+
late_types = []
|
|
50
|
+
new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) }
|
|
51
|
+
missed_late_types = 0
|
|
52
|
+
while (late_type_vals = late_types.shift)
|
|
53
|
+
type_owner, lt = late_type_vals
|
|
54
|
+
if lt.is_a?(String)
|
|
55
|
+
type = Member::BuildType.constantize(lt)
|
|
56
|
+
# Reset the counter, since we might succeed next go-round
|
|
57
|
+
missed_late_types = 0
|
|
58
|
+
update_type_owner(type_owner, type)
|
|
59
|
+
add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
|
|
60
|
+
elsif lt.is_a?(LateBoundType)
|
|
61
|
+
if (type = get_type(lt.name))
|
|
62
|
+
# Reset the counter, since we might succeed next go-round
|
|
63
|
+
missed_late_types = 0
|
|
64
|
+
update_type_owner(type_owner, type)
|
|
65
|
+
add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
|
|
66
|
+
else
|
|
67
|
+
missed_late_types += 1
|
|
68
|
+
# Add it back to the list, maybe we'll be able to resolve it later.
|
|
69
|
+
late_types << [type_owner, lt]
|
|
70
|
+
if missed_late_types == late_types.size
|
|
71
|
+
# We've looked at all of them and haven't resolved one.
|
|
72
|
+
raise UnresolvedLateBoundTypeError.new(type: lt)
|
|
73
|
+
else
|
|
74
|
+
# Try the next one
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
raise ArgumentError, "Unexpected late type: #{lt.inspect}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def update_type_owner(owner, type)
|
|
85
|
+
case owner
|
|
86
|
+
when Module
|
|
87
|
+
if owner.kind.union?
|
|
88
|
+
# It's a union with possible_types
|
|
89
|
+
# Replace the item by class name
|
|
90
|
+
owner.assign_type_membership_object_type(type)
|
|
91
|
+
@possible_types[owner.graphql_name] = owner.possible_types
|
|
92
|
+
elsif type.kind.interface? && (owner.kind.object? || owner.kind.interface?)
|
|
93
|
+
new_interfaces = []
|
|
94
|
+
owner.interfaces.each do |int_t|
|
|
95
|
+
if int_t.is_a?(String) && int_t == type.graphql_name
|
|
96
|
+
new_interfaces << type
|
|
97
|
+
elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name
|
|
98
|
+
new_interfaces << type
|
|
99
|
+
else
|
|
100
|
+
# Don't re-add proper interface definitions,
|
|
101
|
+
# they were probably already added, maybe with options.
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
owner.implements(*new_interfaces)
|
|
105
|
+
new_interfaces.each do |int|
|
|
106
|
+
pt = @possible_types[int.graphql_name] ||= []
|
|
107
|
+
if !pt.include?(owner) && owner.is_a?(Class)
|
|
108
|
+
pt << owner
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
when nil
|
|
113
|
+
# It's a root type
|
|
114
|
+
@types[type.graphql_name] = type
|
|
115
|
+
when GraphQL::Schema::Field, GraphQL::Schema::Argument
|
|
116
|
+
orig_type = owner.type
|
|
117
|
+
# Apply list/non-null wrapper as needed
|
|
118
|
+
if orig_type.respond_to?(:of_type)
|
|
119
|
+
transforms = []
|
|
120
|
+
while (orig_type.respond_to?(:of_type))
|
|
121
|
+
if orig_type.kind.non_null?
|
|
122
|
+
transforms << :to_non_null_type
|
|
123
|
+
elsif orig_type.kind.list?
|
|
124
|
+
transforms << :to_list_type
|
|
125
|
+
else
|
|
126
|
+
raise "Invariant: :of_type isn't non-null or list"
|
|
127
|
+
end
|
|
128
|
+
orig_type = orig_type.of_type
|
|
129
|
+
end
|
|
130
|
+
transforms.reverse_each { |t| type = type.public_send(t) }
|
|
131
|
+
end
|
|
132
|
+
owner.type = type
|
|
133
|
+
else
|
|
134
|
+
raise "Unexpected update: #{owner.inspect} #{type.inspect}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def add_type(type, owner:, late_types:, path:)
|
|
139
|
+
if type.respond_to?(:metadata) && type.metadata.is_a?(Hash)
|
|
140
|
+
type_class = type.metadata[:type_class]
|
|
141
|
+
if type_class.nil?
|
|
142
|
+
raise ArgumentError, "Can't add legacy type: #{type} (#{type.class})"
|
|
143
|
+
else
|
|
144
|
+
type = type_class
|
|
145
|
+
end
|
|
146
|
+
elsif type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType)
|
|
147
|
+
late_types << [owner, type]
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if owner.is_a?(Class) && owner < GraphQL::Schema::Union
|
|
152
|
+
um = @union_memberships[type.graphql_name] ||= []
|
|
153
|
+
um << owner
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if (prev_type = get_local_type(type.graphql_name)) && prev_type == type
|
|
157
|
+
# No need to re-visit
|
|
158
|
+
elsif type.is_a?(Class) && type < GraphQL::Schema::Directive
|
|
159
|
+
@directives << type
|
|
160
|
+
type.all_argument_definitions.each do |arg|
|
|
161
|
+
arg_type = arg.type.unwrap
|
|
162
|
+
references_to(arg_type, from: arg)
|
|
163
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
|
|
164
|
+
if arg.default_value?
|
|
165
|
+
@arguments_with_default_values << arg
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
else
|
|
169
|
+
prev_type = @types[type.graphql_name]
|
|
170
|
+
if prev_type.nil?
|
|
171
|
+
@types[type.graphql_name] = type
|
|
172
|
+
elsif prev_type.is_a?(Array)
|
|
173
|
+
prev_type << type
|
|
174
|
+
else
|
|
175
|
+
@types[type.graphql_name] = [prev_type, type]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
add_directives_from(type)
|
|
179
|
+
if type.kind.fields?
|
|
180
|
+
type.all_field_definitions.each do |field|
|
|
181
|
+
name = field.graphql_name
|
|
182
|
+
field_type = field.type.unwrap
|
|
183
|
+
references_to(field_type, from: field)
|
|
184
|
+
field_path = path + [name]
|
|
185
|
+
add_type(field_type, owner: field, late_types: late_types, path: field_path)
|
|
186
|
+
add_directives_from(field)
|
|
187
|
+
field.all_argument_definitions.each do |arg|
|
|
188
|
+
add_directives_from(arg)
|
|
189
|
+
arg_type = arg.type.unwrap
|
|
190
|
+
references_to(arg_type, from: arg)
|
|
191
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg.graphql_name])
|
|
192
|
+
if arg.default_value?
|
|
193
|
+
@arguments_with_default_values << arg
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
if type.kind.input_object?
|
|
199
|
+
type.all_argument_definitions.each do |arg|
|
|
200
|
+
add_directives_from(arg)
|
|
201
|
+
arg_type = arg.type.unwrap
|
|
202
|
+
references_to(arg_type, from: arg)
|
|
203
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
|
|
204
|
+
if arg.default_value?
|
|
205
|
+
@arguments_with_default_values << arg
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
if type.kind.union?
|
|
210
|
+
@possible_types[type.graphql_name] = type.all_possible_types
|
|
211
|
+
type.all_possible_types.each do |t|
|
|
212
|
+
add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"])
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
if type.kind.interface?
|
|
216
|
+
type.orphan_types.each do |t|
|
|
217
|
+
add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"])
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
if type.kind.object?
|
|
221
|
+
possible_types_for_this_name = @possible_types[type.graphql_name] ||= []
|
|
222
|
+
possible_types_for_this_name << type
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
if type.kind.object? || type.kind.interface?
|
|
226
|
+
type.interface_type_memberships.each do |interface_type_membership|
|
|
227
|
+
case interface_type_membership
|
|
228
|
+
when Schema::TypeMembership
|
|
229
|
+
interface_type = interface_type_membership.abstract_type
|
|
230
|
+
# We can get these now; we'll have to get late-bound types later
|
|
231
|
+
if interface_type.is_a?(Module) && type.is_a?(Class)
|
|
232
|
+
implementers = @possible_types[interface_type.graphql_name] ||= []
|
|
233
|
+
implementers << type
|
|
234
|
+
end
|
|
235
|
+
when String, Schema::LateBoundType
|
|
236
|
+
interface_type = interface_type_membership
|
|
237
|
+
else
|
|
238
|
+
raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
|
|
239
|
+
end
|
|
240
|
+
add_type(interface_type, owner: type, late_types: late_types, path: path + ["implements"])
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
class Schema
|
|
4
4
|
class Argument
|
|
5
|
-
if !String.method_defined?(:-@)
|
|
6
|
-
using GraphQL::StringDedupBackport
|
|
7
|
-
end
|
|
8
|
-
|
|
9
5
|
include GraphQL::Schema::Member::CachedGraphQLDefinition
|
|
10
6
|
include GraphQL::Schema::Member::AcceptsDefinition
|
|
11
7
|
include GraphQL::Schema::Member::HasPath
|
|
@@ -41,7 +37,7 @@ module GraphQL
|
|
|
41
37
|
# @param arg_name [Symbol]
|
|
42
38
|
# @param type_expr
|
|
43
39
|
# @param desc [String]
|
|
44
|
-
# @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable
|
|
40
|
+
# @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
|
|
45
41
|
# @param description [String]
|
|
46
42
|
# @param default_value [Object]
|
|
47
43
|
# @param as [Symbol] Override the keyword name when passed to a method
|
|
@@ -52,12 +48,12 @@ module GraphQL
|
|
|
52
48
|
# @param directives [Hash{Class => Hash}]
|
|
53
49
|
# @param deprecation_reason [String]
|
|
54
50
|
# @param validates [Hash, nil] Options for building validators, if any should be applied
|
|
55
|
-
def initialize(arg_name = nil, type_expr = nil, desc = nil, required
|
|
51
|
+
def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &definition_block)
|
|
56
52
|
arg_name ||= name
|
|
57
53
|
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
|
|
58
54
|
@type_expr = type_expr || type
|
|
59
55
|
@description = desc || description
|
|
60
|
-
@null =
|
|
56
|
+
@null = required != true
|
|
61
57
|
@default_value = default_value
|
|
62
58
|
@owner = owner
|
|
63
59
|
@as = as
|
|
@@ -76,6 +72,9 @@ module GraphQL
|
|
|
76
72
|
end
|
|
77
73
|
|
|
78
74
|
self.validates(validates)
|
|
75
|
+
if required == :nullable
|
|
76
|
+
self.owner.validates(required: { argument: arg_name })
|
|
77
|
+
end
|
|
79
78
|
|
|
80
79
|
if definition_block
|
|
81
80
|
if definition_block.arity == 1
|
|
@@ -86,6 +85,10 @@ module GraphQL
|
|
|
86
85
|
end
|
|
87
86
|
end
|
|
88
87
|
|
|
88
|
+
def inspect
|
|
89
|
+
"#<#{self.class} #{path}: #{type.to_type_signature}#{description ? " @description=#{description.inspect}" : ""}>"
|
|
90
|
+
end
|
|
91
|
+
|
|
89
92
|
# @return [Object] the value used when the client doesn't provide a value for this argument
|
|
90
93
|
attr_reader :default_value
|
|
91
94
|
|
|
@@ -147,20 +150,15 @@ module GraphQL
|
|
|
147
150
|
end
|
|
148
151
|
end
|
|
149
152
|
elsif as_type.kind.input_object?
|
|
150
|
-
as_type.
|
|
151
|
-
input_obj_arg = input_obj_arg.type_class
|
|
152
|
-
# TODO: this skips input objects whose values were alread replaced with application objects.
|
|
153
|
-
# See: https://github.com/rmosolgo/graphql-ruby/issues/2633
|
|
154
|
-
if value.respond_to?(:key?) && value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
|
|
155
|
-
return false
|
|
156
|
-
end
|
|
157
|
-
end
|
|
153
|
+
return as_type.authorized?(obj, value, ctx)
|
|
158
154
|
end
|
|
159
155
|
# None of the early-return conditions were activated,
|
|
160
156
|
# so this is authorized.
|
|
161
157
|
true
|
|
162
158
|
end
|
|
163
159
|
|
|
160
|
+
prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
|
161
|
+
|
|
164
162
|
def to_graphql
|
|
165
163
|
argument = GraphQL::Argument.new
|
|
166
164
|
argument.name = @name
|
|
@@ -240,50 +238,40 @@ module GraphQL
|
|
|
240
238
|
def coerce_into_values(parent_object, values, context, argument_values)
|
|
241
239
|
arg_name = graphql_name
|
|
242
240
|
arg_key = keyword
|
|
243
|
-
has_value = false
|
|
244
241
|
default_used = false
|
|
242
|
+
|
|
245
243
|
if values.key?(arg_name)
|
|
246
|
-
has_value = true
|
|
247
244
|
value = values[arg_name]
|
|
248
245
|
elsif values.key?(arg_key)
|
|
249
|
-
has_value = true
|
|
250
246
|
value = values[arg_key]
|
|
251
247
|
elsif default_value?
|
|
252
|
-
has_value = true
|
|
253
248
|
value = default_value
|
|
254
249
|
default_used = true
|
|
250
|
+
else
|
|
251
|
+
# no value at all
|
|
252
|
+
owner.validate_directive_argument(self, nil)
|
|
253
|
+
return
|
|
255
254
|
end
|
|
256
255
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
end
|
|
256
|
+
loaded_value = nil
|
|
257
|
+
coerced_value = context.schema.error_handler.with_error_handling(context) do
|
|
258
|
+
type.coerce_input(value, context)
|
|
259
|
+
end
|
|
262
260
|
|
|
263
|
-
|
|
261
|
+
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
|
262
|
+
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
|
263
|
+
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
|
|
264
264
|
if loads && !from_resolver?
|
|
265
|
-
loaded_value =
|
|
266
|
-
|
|
267
|
-
context.schema.after_any_lazies(loaded_values) { |result| result }
|
|
268
|
-
else
|
|
269
|
-
context.query.with_error_handling do
|
|
270
|
-
owner.load_application_object(self, loads, coerced_value, context)
|
|
271
|
-
end
|
|
265
|
+
loaded_value = context.query.with_error_handling do
|
|
266
|
+
load_and_authorize_value(owner, coerced_value, context)
|
|
272
267
|
end
|
|
273
268
|
end
|
|
274
269
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
coerced_value
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
|
282
|
-
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
|
283
|
-
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |coerced_value|
|
|
284
|
-
owner.validate_directive_argument(self, coerced_value)
|
|
270
|
+
maybe_loaded_value = loaded_value || resolved_coerced_value
|
|
271
|
+
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
|
|
272
|
+
owner.validate_directive_argument(self, resolved_loaded_value)
|
|
285
273
|
prepared_value = context.schema.error_handler.with_error_handling(context) do
|
|
286
|
-
prepare_value(parent_object,
|
|
274
|
+
prepare_value(parent_object, resolved_loaded_value, context: context)
|
|
287
275
|
end
|
|
288
276
|
|
|
289
277
|
# TODO code smell to access such a deeply-nested constant in a distant module
|
|
@@ -293,9 +281,79 @@ module GraphQL
|
|
|
293
281
|
default_used: default_used,
|
|
294
282
|
)
|
|
295
283
|
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def load_and_authorize_value(load_method_owner, coerced_value, context)
|
|
288
|
+
if coerced_value.nil?
|
|
289
|
+
return nil
|
|
290
|
+
end
|
|
291
|
+
arg_load_method = "load_#{keyword}"
|
|
292
|
+
if load_method_owner.respond_to?(arg_load_method)
|
|
293
|
+
custom_loaded_value = if load_method_owner.is_a?(Class)
|
|
294
|
+
load_method_owner.public_send(arg_load_method, coerced_value, context)
|
|
295
|
+
else
|
|
296
|
+
load_method_owner.public_send(arg_load_method, coerced_value)
|
|
297
|
+
end
|
|
298
|
+
context.schema.after_lazy(custom_loaded_value) do |custom_value|
|
|
299
|
+
if loads
|
|
300
|
+
if type.list?
|
|
301
|
+
loaded_values = custom_value.each_with_index.map { |custom_val, idx|
|
|
302
|
+
id = coerced_value[idx]
|
|
303
|
+
load_method_owner.authorize_application_object(self, id, context, custom_val)
|
|
304
|
+
}
|
|
305
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
|
306
|
+
else
|
|
307
|
+
load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
|
|
308
|
+
end
|
|
309
|
+
else
|
|
310
|
+
custom_value
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
elsif loads
|
|
314
|
+
if type.list?
|
|
315
|
+
loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
|
|
316
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
|
317
|
+
else
|
|
318
|
+
load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
|
|
319
|
+
end
|
|
296
320
|
else
|
|
297
|
-
|
|
298
|
-
|
|
321
|
+
coerced_value
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# @api private
|
|
326
|
+
def validate_default_value
|
|
327
|
+
coerced_default_value = begin
|
|
328
|
+
# This is weird, but we should accept single-item default values for list-type arguments.
|
|
329
|
+
# If we used `coerce_isolated_input` below, it would do this for us, but it's not really
|
|
330
|
+
# the right thing here because we expect default values in application format (Ruby values)
|
|
331
|
+
# not GraphQL format (scalar values).
|
|
332
|
+
#
|
|
333
|
+
# But I don't think Schema::List#coerce_result should apply wrapping to single-item lists.
|
|
334
|
+
prepped_default_value = if default_value.nil?
|
|
335
|
+
nil
|
|
336
|
+
elsif (type.kind.list? || (type.kind.non_null? && type.of_type.list?)) && !default_value.respond_to?(:map)
|
|
337
|
+
[default_value]
|
|
338
|
+
else
|
|
339
|
+
default_value
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
type.coerce_isolated_result(prepped_default_value) unless prepped_default_value.nil?
|
|
343
|
+
rescue GraphQL::Schema::Enum::UnresolvedValueError
|
|
344
|
+
# It raises this, which is helpful at runtime, but not here...
|
|
345
|
+
default_value
|
|
346
|
+
end
|
|
347
|
+
res = type.valid_isolated_input?(coerced_default_value)
|
|
348
|
+
if !res
|
|
349
|
+
raise InvalidDefaultValueError.new(self)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
class InvalidDefaultValueError < GraphQL::Error
|
|
354
|
+
def initialize(argument)
|
|
355
|
+
message = "`#{argument.path}` has an invalid default value: `#{argument.default_value.inspect}` isn't accepted by `#{argument.type.to_type_signature}`; update the default value or the argument type."
|
|
356
|
+
super(message)
|
|
299
357
|
end
|
|
300
358
|
end
|
|
301
359
|
|
|
@@ -3,12 +3,7 @@ require "graphql/schema/build_from_definition/resolve_map"
|
|
|
3
3
|
|
|
4
4
|
module GraphQL
|
|
5
5
|
class Schema
|
|
6
|
-
# TODO Populate `.directive(...)` from here
|
|
7
6
|
module BuildFromDefinition
|
|
8
|
-
if !String.method_defined?(:-@)
|
|
9
|
-
using GraphQL::StringDedupBackport
|
|
10
|
-
end
|
|
11
|
-
|
|
12
7
|
class << self
|
|
13
8
|
# @see {Schema.from_definition}
|
|
14
9
|
def from_definition(definition_string, parser: GraphQL.default_parser, **kwargs)
|
|
@@ -47,10 +42,16 @@ module GraphQL
|
|
|
47
42
|
# _while_ building the schema.
|
|
48
43
|
# It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
|
|
49
44
|
directive_type_resolver = nil
|
|
50
|
-
directive_type_resolver = build_resolve_type(
|
|
45
|
+
directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
|
|
51
46
|
types[type_name] ||= begin
|
|
52
47
|
defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
|
|
53
|
-
|
|
48
|
+
if defn
|
|
49
|
+
build_definition_from_node(defn, directive_type_resolver, default_resolve)
|
|
50
|
+
elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
|
|
51
|
+
built_in_defn
|
|
52
|
+
else
|
|
53
|
+
raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
|
|
54
|
+
end
|
|
54
55
|
end
|
|
55
56
|
})
|
|
56
57
|
|
|
@@ -388,6 +389,11 @@ module GraphQL
|
|
|
388
389
|
include GraphQL::Schema::Interface
|
|
389
390
|
graphql_name(interface_type_definition.name)
|
|
390
391
|
description(interface_type_definition.description)
|
|
392
|
+
interface_type_definition.interfaces.each do |interface_name|
|
|
393
|
+
"Implements: #{interface_type_definition} -> #{interface_name}"
|
|
394
|
+
interface_defn = type_resolver.call(interface_name)
|
|
395
|
+
implements(interface_defn)
|
|
396
|
+
end
|
|
391
397
|
ast_node(interface_type_definition)
|
|
392
398
|
builder.build_directives(self, interface_type_definition, type_resolver)
|
|
393
399
|
|
|
@@ -35,7 +35,7 @@ module GraphQL
|
|
|
35
35
|
GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
argument :by, [String], "Flags to check for this schema member"
|
|
38
|
+
argument :by, [String], "Flags to check for this schema member"
|
|
39
39
|
|
|
40
40
|
module VisibleByFlag
|
|
41
41
|
def self.included(schema_class)
|
|
@@ -44,7 +44,7 @@ module GraphQL
|
|
|
44
44
|
|
|
45
45
|
def visible?(context)
|
|
46
46
|
if dir = self.directives.find { |d| d.is_a?(Flagged) }
|
|
47
|
-
relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f
|
|
47
|
+
relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f # rubocop:disable Development/ContextIsPassedCop -- definition-related
|
|
48
48
|
relevant_flags && relevant_flags.any? && super
|
|
49
49
|
else
|
|
50
50
|
super
|