graphql 1.13.0 → 1.13.24
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 -8
- data/lib/generators/graphql/enum_generator.rb +4 -10
- data/lib/generators/graphql/field_extractor.rb +31 -0
- data/lib/generators/graphql/input_generator.rb +50 -0
- data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
- data/lib/generators/graphql/install_generator.rb +10 -3
- data/lib/generators/graphql/interface_generator.rb +7 -7
- data/lib/generators/graphql/mutation_create_generator.rb +22 -0
- data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
- data/lib/generators/graphql/mutation_generator.rb +5 -30
- data/lib/generators/graphql/mutation_update_generator.rb +22 -0
- data/lib/generators/graphql/object_generator.rb +8 -37
- data/lib/generators/graphql/orm_mutations_base.rb +40 -0
- data/lib/generators/graphql/scalar_generator.rb +4 -2
- data/lib/generators/graphql/templates/enum.erb +5 -1
- data/lib/generators/graphql/templates/input.erb +9 -0
- data/lib/generators/graphql/templates/interface.erb +4 -2
- data/lib/generators/graphql/templates/mutation.erb +1 -1
- data/lib/generators/graphql/templates/mutation_create.erb +20 -0
- data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
- data/lib/generators/graphql/templates/mutation_update.erb +21 -0
- data/lib/generators/graphql/templates/object.erb +4 -2
- data/lib/generators/graphql/templates/scalar.erb +3 -1
- data/lib/generators/graphql/templates/union.erb +4 -2
- data/lib/generators/graphql/type_generator.rb +46 -9
- data/lib/generators/graphql/union_generator.rb +5 -5
- data/lib/graphql/analysis/ast/field_usage.rb +6 -2
- data/lib/graphql/analysis/ast/visitor.rb +2 -1
- data/lib/graphql/argument.rb +1 -1
- data/lib/graphql/base_type.rb +5 -3
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/source.rb +2 -2
- data/lib/graphql/date_encoding_error.rb +16 -0
- data/lib/graphql/define/instance_definable.rb +15 -0
- 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 +1 -1
- data/lib/graphql/enum_type.rb +2 -2
- data/lib/graphql/execution/interpreter/arguments_cache.rb +4 -2
- data/lib/graphql/execution/interpreter/runtime.rb +48 -28
- data/lib/graphql/execution/multiplex.rb +3 -0
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/interface_type.rb +1 -1
- data/lib/graphql/introspection/directive_location_enum.rb +2 -2
- data/lib/graphql/introspection/directive_type.rb +4 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +7 -2
- data/lib/graphql/introspection/type_type.rb +14 -8
- data/lib/graphql/introspection.rb +4 -1
- data/lib/graphql/language/block_string.rb +2 -2
- data/lib/graphql/language/document_from_schema_definition.rb +8 -3
- data/lib/graphql/language/lexer.rb +50 -25
- data/lib/graphql/language/lexer.rl +2 -0
- data/lib/graphql/language/nodes.rb +15 -3
- data/lib/graphql/language/parser.rb +829 -816
- data/lib/graphql/language/parser.y +8 -2
- data/lib/graphql/language/printer.rb +4 -0
- data/lib/graphql/object_type.rb +2 -2
- data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/relation_connection.rb +59 -29
- data/lib/graphql/query/context.rb +10 -0
- data/lib/graphql/query/input_validation_result.rb +9 -0
- data/lib/graphql/query/validation_pipeline.rb +2 -3
- data/lib/graphql/query/variable_validation_error.rb +2 -2
- data/lib/graphql/query/variables.rb +30 -3
- data/lib/graphql/query.rb +0 -1
- data/lib/graphql/relay/connection_type.rb +15 -2
- data/lib/graphql/relay/global_id_resolve.rb +1 -2
- data/lib/graphql/relay/mutation.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/relay/range_add.rb +4 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +4 -4
- data/lib/graphql/scalar_type.rb +1 -1
- data/lib/graphql/schema/argument.rb +29 -16
- data/lib/graphql/schema/build_from_definition.rb +9 -7
- data/lib/graphql/schema/directive.rb +25 -2
- data/lib/graphql/schema/enum.rb +4 -3
- data/lib/graphql/schema/enum_value.rb +3 -1
- data/lib/graphql/schema/field.rb +196 -92
- data/lib/graphql/schema/field_extension.rb +89 -2
- data/lib/graphql/schema/input_object.rb +27 -9
- data/lib/graphql/schema/interface.rb +8 -2
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +21 -4
- data/lib/graphql/schema/loader.rb +3 -0
- data/lib/graphql/schema/member/accepts_definition.rb +7 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +1 -1
- data/lib/graphql/schema/member/has_interfaces.rb +11 -1
- data/lib/graphql/schema/member/validates_input.rb +2 -2
- data/lib/graphql/schema/non_null.rb +9 -3
- data/lib/graphql/schema/object.rb +3 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +8 -0
- data/lib/graphql/schema/resolver.rb +19 -13
- data/lib/graphql/schema/scalar.rb +15 -1
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/union.rb +2 -0
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema/validator.rb +4 -7
- data/lib/graphql/schema/warden.rb +11 -2
- data/lib/graphql/schema.rb +34 -10
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +14 -7
- 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/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +6 -0
- data/lib/graphql/static_validation/validation_context.rb +4 -0
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -0
- data/lib/graphql/subscriptions/serialize.rb +22 -2
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/data_dog_tracing.rb +24 -15
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/tracing/platform_tracing.rb +20 -10
- data/lib/graphql/types/iso_8601_date.rb +13 -5
- data/lib/graphql/types/iso_8601_date_time.rb +8 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +28 -10
- 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/node_field.rb +2 -3
- data/lib/graphql/types/relay/nodes_field.rb +19 -3
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/union_type.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +14 -1
- metadata +56 -30
- /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
- /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
# argument :ingredient_id, ID, required: true
|
15
15
|
# argument :cups, Integer, required: false
|
16
16
|
# argument :tablespoons, Integer, required: false
|
17
|
-
# argument :teaspoons, Integer, required:
|
17
|
+
# argument :teaspoons, Integer, required: false
|
18
18
|
# validates required: { one_of: [:cups, :tablespoons, :teaspoons] }
|
19
19
|
# end
|
20
20
|
#
|
@@ -28,11 +28,23 @@ module GraphQL
|
|
28
28
|
# validates required: { one_of: [:node_id, [:object_type, :object_id]] }
|
29
29
|
# end
|
30
30
|
#
|
31
|
+
# @example require _some_ value for an argument, even if it's null
|
32
|
+
# field :update_settings, AccountSettings do
|
33
|
+
# # `required: :nullable` means this argument must be given, but may be `null`
|
34
|
+
# argument :age, Integer, required: :nullable
|
35
|
+
# end
|
36
|
+
#
|
31
37
|
class RequiredValidator < Validator
|
32
38
|
# @param one_of [Symbol, Array<Symbol>] An argument, or a list of arguments, that represents a valid set of inputs for this field
|
33
39
|
# @param message [String]
|
34
|
-
def initialize(one_of
|
35
|
-
@one_of = one_of
|
40
|
+
def initialize(one_of: nil, argument: nil, message: "%{validated} has the wrong arguments", **default_options)
|
41
|
+
@one_of = if one_of
|
42
|
+
one_of
|
43
|
+
elsif argument
|
44
|
+
[argument]
|
45
|
+
else
|
46
|
+
raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
|
47
|
+
end
|
36
48
|
@message = message
|
37
49
|
super(**default_options)
|
38
50
|
end
|
@@ -40,19 +52,21 @@ module GraphQL
|
|
40
52
|
def validate(_object, _context, value)
|
41
53
|
matched_conditions = 0
|
42
54
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
55
|
+
if !value.nil?
|
56
|
+
@one_of.each do |one_of_condition|
|
57
|
+
case one_of_condition
|
58
|
+
when Symbol
|
59
|
+
if value.key?(one_of_condition)
|
60
|
+
matched_conditions += 1
|
61
|
+
end
|
62
|
+
when Array
|
63
|
+
if one_of_condition.all? { |k| value.key?(k) }
|
64
|
+
matched_conditions += 1
|
65
|
+
break
|
66
|
+
end
|
67
|
+
else
|
68
|
+
raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
|
53
69
|
end
|
54
|
-
else
|
55
|
-
raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
|
56
70
|
end
|
57
71
|
end
|
58
72
|
|
@@ -48,16 +48,13 @@ module GraphQL
|
|
48
48
|
EMPTY_ARRAY
|
49
49
|
else
|
50
50
|
validates_hash = validates_hash.dup
|
51
|
-
allow_null = validates_hash.delete(:allow_null)
|
52
|
-
allow_blank = validates_hash.delete(:allow_blank)
|
53
51
|
|
54
|
-
# This could be {...}.compact on Ruby 2.4+
|
55
52
|
default_options = {}
|
56
|
-
if
|
57
|
-
default_options[:allow_null] = allow_null
|
53
|
+
if validates_hash[:allow_null]
|
54
|
+
default_options[:allow_null] = validates_hash.delete(:allow_null)
|
58
55
|
end
|
59
|
-
if
|
60
|
-
default_options[:allow_blank] = allow_blank
|
56
|
+
if validates_hash[:allow_blank]
|
57
|
+
default_options[:allow_blank] = validates_hash.delete(:allow_blank)
|
61
58
|
end
|
62
59
|
|
63
60
|
# allow_nil or allow_blank are the _only_ validations:
|
@@ -78,6 +78,8 @@ module GraphQL
|
|
78
78
|
def visible_type?(type, ctx); type.visible?(ctx); end
|
79
79
|
def visible_enum_value?(ev, ctx); ev.visible?(ctx); end
|
80
80
|
def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
|
81
|
+
def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
|
82
|
+
def arguments(owner, ctx); owner.arguments(ctx); end
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
@@ -172,8 +174,8 @@ module GraphQL
|
|
172
174
|
|
173
175
|
# @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
|
174
176
|
# @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
|
175
|
-
def arguments(argument_owner)
|
176
|
-
@visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a) } }
|
177
|
+
def arguments(argument_owner, ctx = nil)
|
178
|
+
@visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a, @context) } }
|
177
179
|
@visible_arguments[argument_owner]
|
178
180
|
end
|
179
181
|
|
@@ -231,6 +233,13 @@ module GraphQL
|
|
231
233
|
visible?(type_membership)
|
232
234
|
end
|
233
235
|
|
236
|
+
def interface_type_memberships(obj_type, _ctx = nil)
|
237
|
+
@type_memberships ||= read_through do |obj_t|
|
238
|
+
obj_t.interface_type_memberships
|
239
|
+
end
|
240
|
+
@type_memberships[obj_type]
|
241
|
+
end
|
242
|
+
|
234
243
|
private
|
235
244
|
|
236
245
|
def visible_and_reachable_type?(type_defn)
|
data/lib/graphql/schema.rb
CHANGED
@@ -161,7 +161,7 @@ module GraphQL
|
|
161
161
|
include LazyHandlingMethods
|
162
162
|
extend LazyHandlingMethods
|
163
163
|
|
164
|
-
|
164
|
+
deprecated_accepts_definitions \
|
165
165
|
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
|
166
166
|
:validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
|
167
167
|
:orphan_types, :resolve_type, :type_error, :parse_error,
|
@@ -502,7 +502,7 @@ module GraphQL
|
|
502
502
|
# @param field_name [String]
|
503
503
|
# @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`
|
504
504
|
# @see [GraphQL::Schema::Warden] Restricted access to members of a schema
|
505
|
-
def get_field(parent_type, field_name)
|
505
|
+
def get_field(parent_type, field_name, _context = GraphQL::Query::NullContext)
|
506
506
|
with_definition_error_check do
|
507
507
|
parent_type_name = case parent_type
|
508
508
|
when GraphQL::BaseType, Class, Module
|
@@ -845,7 +845,7 @@ module GraphQL
|
|
845
845
|
# - Cause the Schema instance to be created, if it hasn't been created yet
|
846
846
|
# - Delegate to that instance
|
847
847
|
# Eventually, the methods will be moved into this class, removing the need for the singleton.
|
848
|
-
def_delegators :
|
848
|
+
def_delegators :deprecated_graphql_definition,
|
849
849
|
# Execution
|
850
850
|
:execution_strategy_for_operation,
|
851
851
|
# Configuration
|
@@ -854,6 +854,10 @@ module GraphQL
|
|
854
854
|
:id_from_object=, :object_from_id=,
|
855
855
|
:remove_handler
|
856
856
|
|
857
|
+
def deprecated_graphql_definition
|
858
|
+
graphql_definition(silence_deprecation_warning: true)
|
859
|
+
end
|
860
|
+
|
857
861
|
# @return [GraphQL::Subscriptions]
|
858
862
|
attr_accessor :subscriptions
|
859
863
|
|
@@ -888,6 +892,17 @@ module GraphQL
|
|
888
892
|
GraphQL::Language::DocumentFromSchemaDefinition.new(self).document
|
889
893
|
end
|
890
894
|
|
895
|
+
# @return [String, nil]
|
896
|
+
def description(new_description = nil)
|
897
|
+
if new_description
|
898
|
+
@description = new_description
|
899
|
+
elsif defined?(@description)
|
900
|
+
@description
|
901
|
+
else
|
902
|
+
find_inherited_value(:description, nil)
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
891
906
|
def find(path)
|
892
907
|
if !@finder
|
893
908
|
@find_cache = {}
|
@@ -896,8 +911,15 @@ module GraphQL
|
|
896
911
|
@find_cache[path] ||= @finder.find(path)
|
897
912
|
end
|
898
913
|
|
899
|
-
def graphql_definition
|
900
|
-
@graphql_definition ||=
|
914
|
+
def graphql_definition(silence_deprecation_warning: false)
|
915
|
+
@graphql_definition ||= begin
|
916
|
+
unless silence_deprecation_warning
|
917
|
+
message = "Legacy `.graphql_definition` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Use a class-based definition instead."
|
918
|
+
caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
|
919
|
+
GraphQL::Deprecation.warn(message + caller_message)
|
920
|
+
end
|
921
|
+
to_graphql(silence_deprecation_warning: silence_deprecation_warning)
|
922
|
+
end
|
901
923
|
end
|
902
924
|
|
903
925
|
def default_filter
|
@@ -929,19 +951,20 @@ module GraphQL
|
|
929
951
|
find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
|
930
952
|
end
|
931
953
|
|
954
|
+
prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
932
955
|
def to_graphql
|
933
956
|
schema_defn = self.new
|
934
957
|
schema_defn.raise_definition_error = true
|
935
|
-
schema_defn.query = query && query.graphql_definition
|
936
|
-
schema_defn.mutation = mutation && mutation.graphql_definition
|
937
|
-
schema_defn.subscription = subscription && subscription.graphql_definition
|
958
|
+
schema_defn.query = query && query.graphql_definition(silence_deprecation_warning: true)
|
959
|
+
schema_defn.mutation = mutation && mutation.graphql_definition(silence_deprecation_warning: true)
|
960
|
+
schema_defn.subscription = subscription && subscription.graphql_definition(silence_deprecation_warning: true)
|
938
961
|
schema_defn.validate_timeout = validate_timeout
|
939
962
|
schema_defn.validate_max_errors = validate_max_errors
|
940
963
|
schema_defn.max_complexity = max_complexity
|
941
964
|
schema_defn.error_bubbling = error_bubbling
|
942
965
|
schema_defn.max_depth = max_depth
|
943
966
|
schema_defn.default_max_page_size = default_max_page_size
|
944
|
-
schema_defn.orphan_types = orphan_types.map(
|
967
|
+
schema_defn.orphan_types = orphan_types.map { |t| t.graphql_definition(silence_deprecation_warning: true) }
|
945
968
|
schema_defn.disable_introspection_entry_points = disable_introspection_entry_points?
|
946
969
|
schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point?
|
947
970
|
schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point?
|
@@ -1235,7 +1258,7 @@ module GraphQL
|
|
1235
1258
|
when Module
|
1236
1259
|
type_or_name
|
1237
1260
|
else
|
1238
|
-
raise
|
1261
|
+
raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
|
1239
1262
|
end
|
1240
1263
|
|
1241
1264
|
if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context))
|
@@ -1707,6 +1730,7 @@ module GraphQL
|
|
1707
1730
|
{
|
1708
1731
|
backtrace: ctx[:backtrace],
|
1709
1732
|
tracers: ctx[:tracers],
|
1733
|
+
dataloader: ctx[:dataloader],
|
1710
1734
|
}
|
1711
1735
|
else
|
1712
1736
|
{}
|
@@ -33,6 +33,7 @@ module GraphQL
|
|
33
33
|
GraphQL::StaticValidation::VariablesAreUsedAndDefined,
|
34
34
|
GraphQL::StaticValidation::VariableUsagesAreAllowed,
|
35
35
|
GraphQL::StaticValidation::MutationRootExists,
|
36
|
+
GraphQL::StaticValidation::QueryRootExists,
|
36
37
|
GraphQL::StaticValidation::SubscriptionRootExists,
|
37
38
|
GraphQL::StaticValidation::InputObjectNamesAreUnique,
|
38
39
|
]
|
@@ -110,7 +110,7 @@ module GraphQL
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def on_directive(node, parent)
|
113
|
-
directive_defn = @
|
113
|
+
directive_defn = @context.schema_directives[node.name]
|
114
114
|
@directive_definitions.push(directive_defn)
|
115
115
|
super
|
116
116
|
@directive_definitions.pop
|
@@ -50,15 +50,15 @@ module GraphQL
|
|
50
50
|
@arg_conflicts = nil
|
51
51
|
|
52
52
|
yield
|
53
|
-
|
54
|
-
field_conflicts.each_value { |error| add_error(error) }
|
55
|
-
arg_conflicts.each_value { |error| add_error(error) }
|
53
|
+
# don't initialize these if they weren't initialized in the block:
|
54
|
+
@field_conflicts && @field_conflicts.each_value { |error| add_error(error) }
|
55
|
+
@arg_conflicts && @arg_conflicts.each_value { |error| add_error(error) }
|
56
56
|
end
|
57
57
|
|
58
58
|
def conflicts_within_selection_set(node, parent_type)
|
59
59
|
return if parent_type.nil?
|
60
60
|
|
61
|
-
fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents:
|
61
|
+
fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: nil)
|
62
62
|
|
63
63
|
# (A) Find find all conflicts "within" the fields of this selection set.
|
64
64
|
find_conflicts_within(fields)
|
@@ -198,10 +198,14 @@ module GraphQL
|
|
198
198
|
response_keys.each do |key, fields|
|
199
199
|
next if fields.size < 2
|
200
200
|
# find conflicts within nodes
|
201
|
-
|
202
|
-
|
201
|
+
i = 0
|
202
|
+
while i < fields.size
|
203
|
+
j = i + 1
|
204
|
+
while j < fields.size
|
203
205
|
find_conflict(key, fields[i], fields[j])
|
206
|
+
j += 1
|
204
207
|
end
|
208
|
+
i += 1
|
205
209
|
end
|
206
210
|
end
|
207
211
|
end
|
@@ -243,7 +247,9 @@ module GraphQL
|
|
243
247
|
end
|
244
248
|
|
245
249
|
def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:)
|
246
|
-
return if field1.definition.nil? ||
|
250
|
+
return if field1.definition.nil? ||
|
251
|
+
field2.definition.nil? ||
|
252
|
+
(field1.node.selections.empty? && field2.node.selections.empty?)
|
247
253
|
|
248
254
|
return_type1 = field1.definition.type.unwrap
|
249
255
|
return_type2 = field2.definition.type.unwrap
|
@@ -323,6 +329,7 @@ module GraphQL
|
|
323
329
|
if node.selections.empty?
|
324
330
|
NO_SELECTIONS
|
325
331
|
else
|
332
|
+
parents ||= []
|
326
333
|
fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: [])
|
327
334
|
response_keys = fields.group_by { |f| f.node.alias || f.node.name }
|
328
335
|
[response_keys, fragment_spreads]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module StaticValidation
|
4
|
+
module QueryRootExists
|
5
|
+
def on_operation_definition(node, _parent)
|
6
|
+
if (node.operation_type == 'query' || node.operation_type.nil?) && context.warden.root_type_for_operation("query").nil?
|
7
|
+
add_error(GraphQL::StaticValidation::QueryRootExistsError.new(
|
8
|
+
'Schema is not configured for queries',
|
9
|
+
nodes: node
|
10
|
+
))
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module StaticValidation
|
4
|
+
class QueryRootExistsError < StaticValidation::Error
|
5
|
+
|
6
|
+
def initialize(message, path: nil, nodes: [])
|
7
|
+
super(message, path: path, nodes: nodes)
|
8
|
+
end
|
9
|
+
|
10
|
+
# A hash representation of this Message
|
11
|
+
def to_h
|
12
|
+
extensions = {
|
13
|
+
"code" => code,
|
14
|
+
}
|
15
|
+
|
16
|
+
super.merge({
|
17
|
+
"extensions" => extensions
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
def code
|
22
|
+
"missingQueryConfiguration"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def on_directive(node, _parent)
|
11
|
-
directive_defn = context.
|
11
|
+
directive_defn = context.schema_directives[node.name]
|
12
12
|
assert_required_args(node, directive_defn)
|
13
13
|
super
|
14
14
|
end
|
@@ -16,6 +16,8 @@ module GraphQL
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def assert_required_args(ast_node, defn)
|
19
|
+
args = defn.arguments(context.query.context)
|
20
|
+
return if args.empty?
|
19
21
|
present_argument_names = ast_node.arguments.map(&:name)
|
20
22
|
required_argument_names = context.warden.arguments(defn)
|
21
23
|
.select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
|
@@ -40,7 +40,7 @@ module GraphQL
|
|
40
40
|
nodes: [used_directives[directive_name], ast_directive],
|
41
41
|
directive: directive_name,
|
42
42
|
))
|
43
|
-
|
43
|
+
elsif !((dir_defn = context.schema_directives[directive_name]) && dir_defn.repeatable?)
|
44
44
|
used_directives[directive_name] = ast_directive
|
45
45
|
end
|
46
46
|
end
|
@@ -68,6 +68,12 @@ module GraphQL
|
|
68
68
|
arg_defn = context.warden.get_argument(argument_owner, arg_node.name)
|
69
69
|
arg_defn_type = arg_defn.type
|
70
70
|
|
71
|
+
# If the argument is non-null, but it was given a default value,
|
72
|
+
# then treat it as nullable in practice, see https://github.com/rmosolgo/graphql-ruby/issues/3793
|
73
|
+
if arg_defn_type.non_null? && arg_defn.default_value?
|
74
|
+
arg_defn_type = arg_defn_type.of_type
|
75
|
+
end
|
76
|
+
|
71
77
|
var_inner_type = var_type.unwrap
|
72
78
|
arg_inner_type = arg_defn_type.unwrap
|
73
79
|
|
data/lib/graphql/string_type.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition
|
2
|
+
GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition(silence_deprecation_warning: true)
|
@@ -216,6 +216,8 @@ module GraphQL
|
|
216
216
|
# The channel was closed, forget about it.
|
217
217
|
def delete_subscription(subscription_id)
|
218
218
|
query = @subscriptions.delete(subscription_id)
|
219
|
+
# In case this came from the server, tell the client to unsubscribe:
|
220
|
+
@action_cable.server.broadcast(stream_subscription_name(subscription_id), { more: false })
|
219
221
|
# This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel,
|
220
222
|
# see https://github.com/rmosolgo/graphql-ruby/issues/2478
|
221
223
|
if query
|
@@ -71,9 +71,17 @@ module GraphQL
|
|
71
71
|
when SYMBOL_KEY
|
72
72
|
value[SYMBOL_KEY].to_sym
|
73
73
|
when TIMESTAMP_KEY
|
74
|
-
timestamp_class_name,
|
74
|
+
timestamp_class_name, *timestamp_args = value[TIMESTAMP_KEY]
|
75
75
|
timestamp_class = Object.const_get(timestamp_class_name)
|
76
|
-
|
76
|
+
if defined?(ActiveSupport::TimeWithZone) && timestamp_class <= ActiveSupport::TimeWithZone
|
77
|
+
zone_name, timestamp_s = timestamp_args
|
78
|
+
zone = ActiveSupport::TimeZone[zone_name]
|
79
|
+
raise "Zone #{zone_name} not found, unable to deserialize" unless zone
|
80
|
+
zone.strptime(timestamp_s, TIMESTAMP_FORMAT)
|
81
|
+
else
|
82
|
+
timestamp_s = timestamp_args.first
|
83
|
+
timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
|
84
|
+
end
|
77
85
|
when OPEN_STRUCT_KEY
|
78
86
|
ostruct_values = load_value(value[OPEN_STRUCT_KEY])
|
79
87
|
OpenStruct.new(ostruct_values)
|
@@ -123,6 +131,18 @@ module GraphQL
|
|
123
131
|
{ SYMBOL_KEY => obj.to_s }
|
124
132
|
elsif obj.respond_to?(:to_gid_param)
|
125
133
|
{GLOBALID_KEY => obj.to_gid_param}
|
134
|
+
elsif defined?(ActiveSupport::TimeWithZone) && obj.is_a?(ActiveSupport::TimeWithZone) && obj.class.name != Time.name
|
135
|
+
# This handles a case where Rails prior to 7 would
|
136
|
+
# make the class ActiveSupport::TimeWithZone return "Time" for
|
137
|
+
# its name. In Rails 7, it will now return "ActiveSupport::TimeWithZone",
|
138
|
+
# which happens to be incompatible with expectations we have
|
139
|
+
# with what a Time class supports ( notably, strptime in `load_value` ).
|
140
|
+
#
|
141
|
+
# This now passes along the name of the zone, such that a future deserialization
|
142
|
+
# of this string will use the correct time zone from the ActiveSupport TimeZone
|
143
|
+
# list to produce the time.
|
144
|
+
#
|
145
|
+
{ TIMESTAMP_KEY => [obj.class.name, obj.time_zone.name, obj.strftime(TIMESTAMP_FORMAT)] }
|
126
146
|
elsif obj.is_a?(Date) || obj.is_a?(Time)
|
127
147
|
# DateTime extends Date; for TimeWithZone, call `.utc` first.
|
128
148
|
{ TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'graphql/tracing/notifications_tracing'
|
4
|
+
|
3
5
|
module GraphQL
|
4
6
|
module Tracing
|
5
7
|
# This implementation forwards events to ActiveSupport::Notifications
|
@@ -8,27 +10,11 @@ module GraphQL
|
|
8
10
|
# @see KEYS for event names
|
9
11
|
module ActiveSupportNotificationsTracing
|
10
12
|
# A cache of frequently-used keys to avoid needless string allocations
|
11
|
-
KEYS =
|
12
|
-
|
13
|
-
"parse" => "parse.graphql",
|
14
|
-
"validate" => "validate.graphql",
|
15
|
-
"analyze_multiplex" => "analyze_multiplex.graphql",
|
16
|
-
"analyze_query" => "analyze_query.graphql",
|
17
|
-
"execute_query" => "execute_query.graphql",
|
18
|
-
"execute_query_lazy" => "execute_query_lazy.graphql",
|
19
|
-
"execute_field" => "execute_field.graphql",
|
20
|
-
"execute_field_lazy" => "execute_field_lazy.graphql",
|
21
|
-
"authorized" => "authorized.graphql",
|
22
|
-
"authorized_lazy" => "authorized_lazy.graphql",
|
23
|
-
"resolve_type" => "resolve_type.graphql",
|
24
|
-
"resolve_type_lazy" => "resolve_type.graphql",
|
25
|
-
}
|
13
|
+
KEYS = NotificationsTracing::KEYS
|
14
|
+
NOTIFICATIONS_ENGINE = NotificationsTracing.new(ActiveSupport::Notifications) if defined?(ActiveSupport::Notifications)
|
26
15
|
|
27
|
-
def self.trace(key, metadata)
|
28
|
-
|
29
|
-
ActiveSupport::Notifications.instrument(prefixed_key, metadata) do
|
30
|
-
yield
|
31
|
-
end
|
16
|
+
def self.trace(key, metadata, &blk)
|
17
|
+
NOTIFICATIONS_ENGINE.trace(key, metadata, &blk)
|
32
18
|
end
|
33
19
|
end
|
34
20
|
end
|
@@ -15,17 +15,23 @@ module GraphQL
|
|
15
15
|
}
|
16
16
|
|
17
17
|
def platform_trace(platform_key, key, data)
|
18
|
-
tracer.trace(platform_key, service:
|
19
|
-
span.
|
18
|
+
tracer.trace(platform_key, service: options[:service], type: 'custom') do |span|
|
19
|
+
span.set_tag('component', 'graphql')
|
20
|
+
span.set_tag('operation', key)
|
20
21
|
|
21
22
|
if key == 'execute_multiplex'
|
22
23
|
operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
|
23
|
-
span.resource = operations unless operations.empty?
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
resource = if operations.empty?
|
26
|
+
first_query = data[:multiplex].queries.first
|
27
|
+
fallback_transaction_name(first_query && first_query.context)
|
28
|
+
else
|
29
|
+
operations
|
28
30
|
end
|
31
|
+
span.resource = resource if resource
|
32
|
+
|
33
|
+
# [Deprecated] will be removed in the future
|
34
|
+
span.set_metric('_dd1.sr.eausr', analytics_sample_rate) if analytics_enabled?
|
29
35
|
end
|
30
36
|
|
31
37
|
if key == 'execute_query'
|
@@ -34,26 +40,29 @@ module GraphQL
|
|
34
40
|
span.set_tag(:query_string, data[:query].query_string)
|
35
41
|
end
|
36
42
|
|
43
|
+
prepare_span(key, data, span)
|
44
|
+
|
37
45
|
yield
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
41
|
-
|
42
|
-
|
49
|
+
# Implement this method in a subclass to apply custom tags to datadog spans
|
50
|
+
# @param key [String] The event being traced
|
51
|
+
# @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
|
52
|
+
# @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
|
53
|
+
def prepare_span(key, data, span)
|
43
54
|
end
|
44
55
|
|
45
56
|
def tracer
|
46
|
-
|
47
|
-
end
|
57
|
+
default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
|
48
58
|
|
49
|
-
|
50
|
-
|
51
|
-
&& Datadog::Contrib::Analytics.respond_to?(:enabled?) \
|
52
|
-
&& Datadog::Contrib::Analytics.respond_to?(:set_sample_rate)
|
59
|
+
# [Deprecated] options[:tracer] will be removed in the future
|
60
|
+
options.fetch(:tracer, default_tracer)
|
53
61
|
end
|
54
62
|
|
55
63
|
def analytics_enabled?
|
56
|
-
|
64
|
+
# [Deprecated] options[:analytics_enabled] will be removed in the future
|
65
|
+
options.fetch(:analytics_enabled, false)
|
57
66
|
end
|
58
67
|
|
59
68
|
def analytics_sample_rate
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Tracing
|
5
|
+
# This implementation forwards events to a notification handler (i.e.
|
6
|
+
# ActiveSupport::Notifications or Dry::Monitor::Notifications)
|
7
|
+
# with a `graphql` suffix.
|
8
|
+
#
|
9
|
+
# @see KEYS for event names
|
10
|
+
class NotificationsTracing
|
11
|
+
# A cache of frequently-used keys to avoid needless string allocations
|
12
|
+
KEYS = {
|
13
|
+
"lex" => "lex.graphql",
|
14
|
+
"parse" => "parse.graphql",
|
15
|
+
"validate" => "validate.graphql",
|
16
|
+
"analyze_multiplex" => "analyze_multiplex.graphql",
|
17
|
+
"analyze_query" => "analyze_query.graphql",
|
18
|
+
"execute_query" => "execute_query.graphql",
|
19
|
+
"execute_query_lazy" => "execute_query_lazy.graphql",
|
20
|
+
"execute_field" => "execute_field.graphql",
|
21
|
+
"execute_field_lazy" => "execute_field_lazy.graphql",
|
22
|
+
"authorized" => "authorized.graphql",
|
23
|
+
"authorized_lazy" => "authorized_lazy.graphql",
|
24
|
+
"resolve_type" => "resolve_type.graphql",
|
25
|
+
"resolve_type_lazy" => "resolve_type.graphql",
|
26
|
+
}
|
27
|
+
|
28
|
+
MAX_KEYS_SIZE = 100
|
29
|
+
|
30
|
+
# Initialize a new NotificationsTracing instance
|
31
|
+
#
|
32
|
+
# @param [Object] notifications_engine The notifications engine to use
|
33
|
+
def initialize(notifications_engine)
|
34
|
+
@notifications_engine = notifications_engine
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sends a GraphQL tracing event to the notification handler
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# . notifications_engine = Dry::Monitor::Notifications.new(:graphql)
|
41
|
+
# . tracer = GraphQL::Tracing::NotificationsTracing.new(notifications_engine)
|
42
|
+
# . tracer.trace("lex") { ... }
|
43
|
+
#
|
44
|
+
# @param [string] key The key for the event
|
45
|
+
# @param [Hash] metadata The metadata for the event
|
46
|
+
# @yield The block to execute for the event
|
47
|
+
def trace(key, metadata, &blk)
|
48
|
+
prefixed_key = KEYS[key] || "#{key}.graphql"
|
49
|
+
|
50
|
+
# Cache the new keys while making sure not to induce a memory leak
|
51
|
+
if KEYS.size < MAX_KEYS_SIZE
|
52
|
+
KEYS[key] ||= prefixed_key
|
53
|
+
end
|
54
|
+
|
55
|
+
@notifications_engine.instrument(prefixed_key, metadata, &blk)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|