graphql 1.12.16 → 1.13.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/object_generator.rb +2 -1
  6. data/lib/generators/graphql/relay.rb +19 -11
  7. data/lib/generators/graphql/templates/schema.erb +14 -2
  8. data/lib/generators/graphql/type_generator.rb +0 -1
  9. data/lib/graphql/analysis/ast/field_usage.rb +3 -3
  10. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  11. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  12. data/lib/graphql/backtrace/table.rb +1 -1
  13. data/lib/graphql/base_type.rb +4 -2
  14. data/lib/graphql/boolean_type.rb +1 -1
  15. data/lib/graphql/dataloader/source.rb +50 -2
  16. data/lib/graphql/dataloader.rb +93 -37
  17. data/lib/graphql/define/instance_definable.rb +1 -1
  18. data/lib/graphql/deprecated_dsl.rb +11 -3
  19. data/lib/graphql/deprecation.rb +1 -5
  20. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  21. data/lib/graphql/directive/include_directive.rb +1 -1
  22. data/lib/graphql/directive/skip_directive.rb +1 -1
  23. data/lib/graphql/directive.rb +0 -4
  24. data/lib/graphql/enum_type.rb +5 -1
  25. data/lib/graphql/execution/errors.rb +1 -0
  26. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  27. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  28. data/lib/graphql/execution/interpreter/runtime.rb +39 -23
  29. data/lib/graphql/execution/lookahead.rb +2 -2
  30. data/lib/graphql/execution/multiplex.rb +4 -1
  31. data/lib/graphql/float_type.rb +1 -1
  32. data/lib/graphql/id_type.rb +1 -1
  33. data/lib/graphql/int_type.rb +1 -1
  34. data/lib/graphql/integer_encoding_error.rb +18 -2
  35. data/lib/graphql/introspection/directive_type.rb +1 -1
  36. data/lib/graphql/introspection/entry_points.rb +2 -2
  37. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  38. data/lib/graphql/introspection/field_type.rb +2 -2
  39. data/lib/graphql/introspection/input_value_type.rb +10 -4
  40. data/lib/graphql/introspection/schema_type.rb +2 -2
  41. data/lib/graphql/introspection/type_type.rb +10 -10
  42. data/lib/graphql/language/block_string.rb +2 -6
  43. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  44. data/lib/graphql/language/lexer.rb +0 -3
  45. data/lib/graphql/language/lexer.rl +0 -4
  46. data/lib/graphql/language/nodes.rb +12 -2
  47. data/lib/graphql/language/parser.rb +442 -434
  48. data/lib/graphql/language/parser.y +5 -4
  49. data/lib/graphql/language/printer.rb +6 -1
  50. data/lib/graphql/language/sanitized_printer.rb +5 -5
  51. data/lib/graphql/language/token.rb +0 -4
  52. data/lib/graphql/name_validator.rb +0 -4
  53. data/lib/graphql/pagination/connections.rb +35 -16
  54. data/lib/graphql/query/arguments.rb +1 -1
  55. data/lib/graphql/query/arguments_cache.rb +1 -1
  56. data/lib/graphql/query/context.rb +15 -2
  57. data/lib/graphql/query/literal_input.rb +1 -1
  58. data/lib/graphql/query/null_context.rb +12 -7
  59. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  60. data/lib/graphql/query/validation_pipeline.rb +1 -1
  61. data/lib/graphql/query/variables.rb +5 -1
  62. data/lib/graphql/query.rb +4 -0
  63. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  64. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  65. data/lib/graphql/relay/page_info.rb +1 -1
  66. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  67. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  68. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  69. data/lib/graphql/rubocop.rb +4 -0
  70. data/lib/graphql/schema/addition.rb +37 -28
  71. data/lib/graphql/schema/argument.rb +79 -34
  72. data/lib/graphql/schema/build_from_definition.rb +5 -5
  73. data/lib/graphql/schema/directive/feature.rb +1 -1
  74. data/lib/graphql/schema/directive/flagged.rb +2 -2
  75. data/lib/graphql/schema/directive/include.rb +1 -1
  76. data/lib/graphql/schema/directive/skip.rb +1 -1
  77. data/lib/graphql/schema/directive/transform.rb +1 -1
  78. data/lib/graphql/schema/directive.rb +7 -3
  79. data/lib/graphql/schema/enum.rb +60 -10
  80. data/lib/graphql/schema/enum_value.rb +6 -0
  81. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  82. data/lib/graphql/schema/field.rb +140 -42
  83. data/lib/graphql/schema/field_extension.rb +52 -2
  84. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  85. data/lib/graphql/schema/finder.rb +5 -5
  86. data/lib/graphql/schema/input_object.rb +13 -14
  87. data/lib/graphql/schema/interface.rb +11 -20
  88. data/lib/graphql/schema/introspection_system.rb +1 -1
  89. data/lib/graphql/schema/list.rb +3 -1
  90. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  91. data/lib/graphql/schema/member/build_type.rb +0 -4
  92. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  93. data/lib/graphql/schema/member/has_arguments.rb +145 -57
  94. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  95. data/lib/graphql/schema/member/has_fields.rb +76 -18
  96. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  97. data/lib/graphql/schema/member.rb +1 -0
  98. data/lib/graphql/schema/non_null.rb +3 -1
  99. data/lib/graphql/schema/object.rb +10 -75
  100. data/lib/graphql/schema/printer.rb +1 -1
  101. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  102. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  103. data/lib/graphql/schema/resolver.rb +49 -64
  104. data/lib/graphql/schema/scalar.rb +2 -0
  105. data/lib/graphql/schema/subscription.rb +17 -9
  106. data/lib/graphql/schema/traversal.rb +1 -1
  107. data/lib/graphql/schema/type_expression.rb +1 -1
  108. data/lib/graphql/schema/type_membership.rb +18 -4
  109. data/lib/graphql/schema/union.rb +8 -1
  110. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  111. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  112. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  113. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  114. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  115. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  116. data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
  117. data/lib/graphql/schema/validator.rb +33 -25
  118. data/lib/graphql/schema/warden.rb +116 -52
  119. data/lib/graphql/schema.rb +124 -27
  120. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  121. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  122. data/lib/graphql/static_validation/error.rb +3 -1
  123. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  124. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  125. data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
  126. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  127. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  128. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  129. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  130. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  131. data/lib/graphql/static_validation/validation_context.rb +8 -2
  132. data/lib/graphql/static_validation/validator.rb +15 -12
  133. data/lib/graphql/string_encoding_error.rb +13 -3
  134. data/lib/graphql/string_type.rb +1 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +15 -5
  136. data/lib/graphql/subscriptions/event.rb +66 -13
  137. data/lib/graphql/subscriptions/serialize.rb +1 -1
  138. data/lib/graphql/subscriptions.rb +17 -19
  139. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  140. data/lib/graphql/types/int.rb +1 -1
  141. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  142. data/lib/graphql/types/relay/default_relay.rb +5 -1
  143. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  144. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  145. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  146. data/lib/graphql/types/string.rb +1 -1
  147. data/lib/graphql/unauthorized_error.rb +1 -1
  148. data/lib/graphql/version.rb +1 -1
  149. data/lib/graphql.rb +10 -32
  150. data/readme.md +1 -1
  151. metadata +13 -6
@@ -15,14 +15,16 @@ module GraphQL
15
15
  extend Forwardable
16
16
 
17
17
  attr_reader :query, :errors, :visitor,
18
- :on_dependency_resolve_handlers
18
+ :on_dependency_resolve_handlers,
19
+ :max_errors
19
20
 
20
21
  def_delegators :@query, :schema, :document, :fragments, :operations, :warden
21
22
 
22
- def initialize(query, visitor_class)
23
+ def initialize(query, visitor_class, max_errors)
23
24
  @query = query
24
25
  @literal_validator = LiteralValidator.new(context: query.context)
25
26
  @errors = []
27
+ @max_errors = max_errors || Float::INFINITY
26
28
  @on_dependency_resolve_handlers = []
27
29
  @visitor = visitor_class.new(document, self)
28
30
  end
@@ -38,6 +40,10 @@ module GraphQL
38
40
  def validate_literal(ast_value, type)
39
41
  @literal_validator.validate(ast_value, type)
40
42
  end
43
+
44
+ def too_many_errors?
45
+ @errors.length >= @max_errors
46
+ end
41
47
  end
42
48
  end
43
49
  end
@@ -24,8 +24,9 @@ module GraphQL
24
24
  # @param query [GraphQL::Query]
25
25
  # @param validate [Boolean]
26
26
  # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
27
+ # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
27
28
  # @return [Array<Hash>]
28
- def validate(query, validate: true, timeout: nil)
29
+ def validate(query, validate: true, timeout: nil, max_errors: nil)
29
30
  query.trace("validate", { validate: validate, query: query }) do
30
31
  can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
31
32
  errors = if validate == false && can_skip_rewrite
@@ -34,25 +35,27 @@ module GraphQL
34
35
  rules_to_use = validate ? @rules : []
35
36
  visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite)
36
37
 
37
- context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
38
+ context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors)
38
39
 
39
40
  begin
40
41
  # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
41
42
  # A timeout value of 0 or nil will execute the block without any timeout.
42
43
  Timeout::timeout(timeout) do
43
- # Attach legacy-style rules.
44
- # Only loop through rules if it has legacy-style rules
45
- unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
46
- legacy_rules.each do |rule_class_or_module|
47
- if rule_class_or_module.method_defined?(:validate)
48
- GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
49
- GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
50
- rule_class_or_module.new.validate(context)
44
+ catch(:too_many_validation_errors) do
45
+ # Attach legacy-style rules.
46
+ # Only loop through rules if it has legacy-style rules
47
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
48
+ legacy_rules.each do |rule_class_or_module|
49
+ if rule_class_or_module.method_defined?(:validate)
50
+ GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
51
+ GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
52
+ rule_class_or_module.new.validate(context)
53
+ end
51
54
  end
52
55
  end
53
- end
54
56
 
55
- context.visitor.visit
57
+ context.visitor.visit
58
+ end
56
59
  end
57
60
  rescue Timeout::Error
58
61
  handle_timeout(query, context)
@@ -1,10 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class StringEncodingError < GraphQL::RuntimeTypeError
4
- attr_reader :string
5
- def initialize(str)
4
+ attr_reader :string, :field, :path
5
+ def initialize(str, context:)
6
6
  @string = str
7
- super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.")
7
+ @field = context[:current_field]
8
+ @path = context[:current_path]
9
+ message = "String #{str.inspect} was encoded as #{str.encoding}".dup
10
+ if @path
11
+ message << " @ #{@path.join(".")}"
12
+ end
13
+ if @field
14
+ message << " (#{@field.path})"
15
+ end
16
+ message << ". GraphQL requires an encoding compatible with UTF-8."
17
+ super(message)
8
18
  end
9
19
  end
10
20
  end
@@ -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)
@@ -127,7 +127,13 @@ module GraphQL
127
127
  # It will receive notifications when events come in
128
128
  # and re-evaluate the query locally.
129
129
  def write_subscription(query, events)
130
- channel = query.context.fetch(:channel)
130
+ unless (channel = query.context[:channel])
131
+ raise GraphQL::Error, "This GraphQL Subscription client does not support the transport protocol expected"\
132
+ "by the backend Subscription Server implementation (graphql-ruby ActionCableSubscriptions in this case)."\
133
+ "Some official client implementation including Apollo (https://graphql-ruby.org/javascript_client/apollo_subscriptions.html), "\
134
+ "Relay Modern (https://graphql-ruby.org/javascript_client/relay_subscriptions.html#actioncable)."\
135
+ "GraphiQL via `graphiql-rails` may not work out of box (#1051)."
136
+ end
131
137
  subscription_id = query.context[:subscription_id] ||= build_id
132
138
  stream = stream_subscription_name(subscription_id)
133
139
  channel.stream_from(stream)
@@ -164,10 +170,12 @@ module GraphQL
164
170
  first_subscription_id = first_event.context.fetch(:subscription_id)
165
171
  object ||= load_action_cable_message(message, first_event.context)
166
172
  result = execute_update(first_subscription_id, first_event, object)
167
- # Having calculated the result _once_, send the same payload to all subscribers
168
- events.each do |event|
169
- subscription_id = event.context.fetch(:subscription_id)
170
- deliver(subscription_id, result)
173
+ if !result.nil?
174
+ # Having calculated the result _once_, send the same payload to all subscribers
175
+ events.each do |event|
176
+ subscription_id = event.context.fetch(:subscription_id)
177
+ deliver(subscription_id, result)
178
+ end
171
179
  end
172
180
  end
173
181
  end
@@ -208,6 +216,8 @@ module GraphQL
208
216
  # The channel was closed, forget about it.
209
217
  def delete_subscription(subscription_id)
210
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 })
211
221
  # This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel,
212
222
  # see https://github.com/rmosolgo/graphql-ruby/issues/2478
213
223
  if query
@@ -23,15 +23,23 @@ module GraphQL
23
23
  @arguments = arguments
24
24
  @context = context
25
25
  field ||= context.field
26
- scope_val = scope || (context && field.subscription_scope && context[field.subscription_scope])
26
+ scope_key = field.subscription_scope
27
+ scope_val = scope || (context && scope_key && context[scope_key])
28
+ if scope_key &&
29
+ (subscription = field.resolver) &&
30
+ (subscription.respond_to?(:subscription_scope_optional?)) &&
31
+ !subscription.subscription_scope_optional? &&
32
+ scope_val.nil?
33
+ raise Subscriptions::SubscriptionScopeMissingError, "#{field.path} (#{subscription}) requires a `scope:` value to trigger updates (Set `subscription_scope ..., optional: true` to disable this requirement)"
34
+ end
27
35
 
28
- @topic = self.class.serialize(name, arguments, field, scope: scope_val)
36
+ @topic = self.class.serialize(name, arguments, field, scope: scope_val, context: context)
29
37
  end
30
38
 
31
39
  # @return [String] an identifier for this unit of subscription
32
- def self.serialize(_name, arguments, field, scope:)
40
+ def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
33
41
  subscription = field.resolver || GraphQL::Schema::Subscription
34
- normalized_args = stringify_args(field, arguments.to_h)
42
+ normalized_args = stringify_args(field, arguments.to_h, context)
35
43
  subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
36
44
  end
37
45
 
@@ -53,37 +61,82 @@ module GraphQL
53
61
 
54
62
  class << self
55
63
  private
56
- def stringify_args(arg_owner, args)
64
+
65
+ # This method does not support cyclic references in the Hash,
66
+ # nor does it support Hashes whose keys are not sortable
67
+ # with respect to their peers ( cases where a <=> b might throw an error )
68
+ def deep_sort_hash_keys(hash_to_sort)
69
+ raise ArgumentError.new("Argument must be a Hash") unless hash_to_sort.is_a?(Hash)
70
+ hash_to_sort.keys.sort.map do |k|
71
+ if hash_to_sort[k].is_a?(Hash)
72
+ [k, deep_sort_hash_keys(hash_to_sort[k])]
73
+ elsif hash_to_sort[k].is_a?(Array)
74
+ [k, deep_sort_array_hashes(hash_to_sort[k])]
75
+ else
76
+ [k, hash_to_sort[k]]
77
+ end
78
+ end.to_h
79
+ end
80
+
81
+ def deep_sort_array_hashes(array_to_inspect)
82
+ raise ArgumentError.new("Argument must be an Array") unless array_to_inspect.is_a?(Array)
83
+ array_to_inspect.map do |v|
84
+ if v.is_a?(Hash)
85
+ deep_sort_hash_keys(v)
86
+ elsif v.is_a?(Array)
87
+ deep_sort_array_hashes(v)
88
+ else
89
+ v
90
+ end
91
+ end
92
+ end
93
+
94
+ def stringify_args(arg_owner, args, context)
95
+ arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
57
96
  case args
58
97
  when Hash
59
98
  next_args = {}
60
99
  args.each do |k, v|
61
100
  arg_name = k.to_s
62
101
  camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name)
63
- arg_defn = get_arg_definition(arg_owner, camelized_arg_name)
102
+ arg_defn = get_arg_definition(arg_owner, camelized_arg_name, context)
64
103
 
65
104
  if arg_defn
66
105
  normalized_arg_name = camelized_arg_name
67
106
  else
68
107
  normalized_arg_name = arg_name
69
- arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
108
+ arg_defn = get_arg_definition(arg_owner, normalized_arg_name, context)
109
+ end
110
+ arg_base_type = arg_defn.type.unwrap
111
+ # In the case where the value being emitted is seen as a "JSON"
112
+ # type, treat the value as one atomic unit of serialization
113
+ is_json_definition = arg_base_type && arg_base_type <= GraphQL::Types::JSON
114
+ if is_json_definition
115
+ sorted_value = if v.is_a?(Hash)
116
+ deep_sort_hash_keys(v)
117
+ elsif v.is_a?(Array)
118
+ deep_sort_array_hashes(v)
119
+ else
120
+ v
121
+ end
122
+ next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value
123
+ else
124
+ next_args[normalized_arg_name] = stringify_args(arg_base_type, v, context)
70
125
  end
71
-
72
- next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
73
126
  end
74
127
  # Make sure they're deeply sorted
75
128
  next_args.sort.to_h
76
129
  when Array
77
- args.map { |a| stringify_args(arg_owner, a) }
130
+ args.map { |a| stringify_args(arg_owner, a, context) }
78
131
  when GraphQL::Schema::InputObject
79
- stringify_args(arg_owner, args.to_h)
132
+ stringify_args(arg_owner, args.to_h, context)
80
133
  else
81
134
  args
82
135
  end
83
136
  end
84
137
 
85
- def get_arg_definition(arg_owner, arg_name)
86
- arg_owner.arguments[arg_name] || arg_owner.arguments.each_value.find { |v| v.keyword.to_s == arg_name }
138
+ def get_arg_definition(arg_owner, arg_name, context)
139
+ arg_owner.get_argument(arg_name, context) || arg_owner.arguments(context).each_value.find { |v| v.keyword.to_s == arg_name }
87
140
  end
88
141
  end
89
142
  end
@@ -9,7 +9,7 @@ module GraphQL
9
9
  SYMBOL_KEY = "__sym__"
10
10
  SYMBOL_KEYS_KEY = "__sym_keys__"
11
11
  TIMESTAMP_KEY = "__timestamp__"
12
- TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%Z" # eg '2020-01-01 23:59:59.123456789+05:00'
12
+ TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00'
13
13
  OPEN_STRUCT_KEY = "__ostruct__"
14
14
 
15
15
  module_function
@@ -16,6 +16,13 @@ module GraphQL
16
16
  class InvalidTriggerError < GraphQL::Error
17
17
  end
18
18
 
19
+ # Raised when either:
20
+ # - An initial subscription didn't have a value for `context[subscription_scope]`
21
+ # - Or, an update didn't pass `.trigger(..., scope:)`
22
+ # When raised, the initial subscription or update fails completely.
23
+ class SubscriptionScopeMissingError < GraphQL::Error
24
+ end
25
+
19
26
  # @see {Subscriptions#initialize} for options, concrete implementations may add options.
20
27
  def self.use(defn, options = {})
21
28
  schema = defn.is_a?(Class) ? defn : defn.target
@@ -76,7 +83,8 @@ module GraphQL
76
83
  end
77
84
 
78
85
  # Normalize symbol-keyed args to strings, try camelizing them
79
- normalized_args = normalize_arguments(normalized_event_name, field, args)
86
+ # Should this accept a real context somehow?
87
+ normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)
80
88
 
81
89
  event = Subscriptions::Event.new(
82
90
  name: normalized_event_name,
@@ -151,16 +159,6 @@ module GraphQL
151
159
  # @param object [Object]
152
160
  # @return [void]
153
161
  def execute_all(event, object)
154
- each_subscription_id(event) do |subscription_id|
155
- execute(subscription_id, event, object)
156
- end
157
- end
158
-
159
- # Get each `subscription_id` subscribed to `event.topic` and yield them
160
- # @param event [GraphQL::Subscriptions::Event]
161
- # @yieldparam subscription_id [String]
162
- # @return [void]
163
- def each_subscription_id(event)
164
162
  raise GraphQL::RequiredImplementationMissingError
165
163
  end
166
164
 
@@ -233,7 +231,7 @@ module GraphQL
233
231
  # @param arg_owner [GraphQL::Field, GraphQL::BaseType]
234
232
  # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner`
235
233
  # @return [Any] normalized arguments value
236
- def normalize_arguments(event_name, arg_owner, args)
234
+ def normalize_arguments(event_name, arg_owner, args, context)
237
235
  case arg_owner
238
236
  when GraphQL::Field, GraphQL::InputObjectType, GraphQL::Schema::Field, Class
239
237
  if arg_owner.is_a?(Class) && !arg_owner.kind.input_object?
@@ -244,19 +242,19 @@ module GraphQL
244
242
  missing_arg_names = []
245
243
  args.each do |k, v|
246
244
  arg_name = k.to_s
247
- arg_defn = arg_owner.arguments[arg_name]
245
+ arg_defn = arg_owner.get_argument(arg_name, context)
248
246
  if arg_defn
249
247
  normalized_arg_name = arg_name
250
248
  else
251
249
  normalized_arg_name = normalize_name(arg_name)
252
- arg_defn = arg_owner.arguments[normalized_arg_name]
250
+ arg_defn = arg_owner.get_argument(normalized_arg_name, context)
253
251
  end
254
252
 
255
253
  if arg_defn
256
254
  if arg_defn.loads
257
255
  normalized_arg_name = arg_defn.keyword.to_s
258
256
  end
259
- normalized = normalize_arguments(event_name, arg_defn.type, v)
257
+ normalized = normalize_arguments(event_name, arg_defn.type, v, context)
260
258
  normalized_args[normalized_arg_name] = normalized
261
259
  else
262
260
  # Couldn't find a matching argument definition
@@ -266,12 +264,12 @@ module GraphQL
266
264
 
267
265
  # Backfill default values so that trigger arguments
268
266
  # match query arguments.
269
- arg_owner.arguments.each do |name, arg_defn|
267
+ arg_owner.arguments(context).each do |_name, arg_defn|
270
268
  if arg_defn.default_value? && !normalized_args.key?(arg_defn.name)
271
269
  default_value = arg_defn.default_value
272
270
  # We don't have an underlying "object" here, so it can't call methods.
273
271
  # This is broken.
274
- normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: GraphQL::Query::NullContext)
272
+ normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: context)
275
273
  end
276
274
  end
277
275
 
@@ -290,9 +288,9 @@ module GraphQL
290
288
 
291
289
  normalized_args
292
290
  when GraphQL::ListType, GraphQL::Schema::List
293
- args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) }
291
+ args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a, context) }
294
292
  when GraphQL::NonNullType, GraphQL::Schema::NonNull
295
- normalize_arguments(event_name, arg_owner.of_type, args)
293
+ normalize_arguments(event_name, arg_owner.of_type, args, context)
296
294
  else
297
295
  args
298
296
  end
@@ -14,7 +14,22 @@ module GraphQL
14
14
  "execute_query_lazy" => "execute.graphql",
15
15
  }
16
16
 
17
+ # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
18
+ # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
19
+ # It can also be specified per-query with `context[:set_appsignal_action_name]`.
20
+ def initialize(options = {})
21
+ @set_action_name = options.fetch(:set_action_name, false)
22
+ super
23
+ end
24
+
17
25
  def platform_trace(platform_key, key, data)
26
+ if key == "execute_query"
27
+ set_this_txn_name = data[:query].context[:set_appsignal_action_name]
28
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name)
29
+ Appsignal::Transaction.current.set_action(transaction_name(data[:query]))
30
+ end
31
+ end
32
+
18
33
  Appsignal.instrument(platform_key) do
19
34
  yield
20
35
  end
@@ -25,7 +25,7 @@ module GraphQL
25
25
  if value >= MIN && value <= MAX
26
26
  value
27
27
  else
28
- err = GraphQL::IntegerEncodingError.new(value)
28
+ err = GraphQL::IntegerEncodingError.new(value, context: ctx)
29
29
  ctx.schema.type_error(err, ctx)
30
30
  end
31
31
  end
@@ -35,7 +35,8 @@ module GraphQL
35
35
  # It's called when you subclass this base connection, trying to use the
36
36
  # class name to set defaults. You can call it again in the class definition
37
37
  # to override the default (or provide a value, if the default lookup failed).
38
- def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: self.has_nodes_field, node_nullable: self.node_nullable, edges_nullable: self.edges_nullable, edge_nullable: self.edge_nullable)
38
+ # @param field_options [Hash] Any extra keyword arguments to pass to the `field :edges, ...` and `field :nodes, ...` configurations
39
+ def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: self.has_nodes_field, node_nullable: self.node_nullable, edges_nullable: self.edges_nullable, edge_nullable: self.edge_nullable, field_options: nil)
39
40
  # Set this connection's graphql name
40
41
  node_type_name = node_type.graphql_name
41
42
 
@@ -43,13 +44,22 @@ module GraphQL
43
44
  @edge_type = edge_type_class
44
45
  @edge_class = edge_class
45
46
 
46
- field :edges, [edge_type_class, null: edge_nullable],
47
+ base_field_options = {
48
+ name: :edges,
49
+ type: [edge_type_class, null: edge_nullable],
47
50
  null: edges_nullable,
48
51
  description: "A list of edges.",
49
52
  legacy_edge_class: edge_class, # This is used by the old runtime only, for EdgesInstrumentation
50
- connection: false
53
+ connection: false,
54
+ }
51
55
 
52
- define_nodes_field(node_nullable) if nodes_field
56
+ if field_options
57
+ base_field_options.merge!(field_options)
58
+ end
59
+
60
+ field(**base_field_options)
61
+
62
+ define_nodes_field(node_nullable, field_options: field_options) if nodes_field
53
63
 
54
64
  description("The connection type for #{node_type_name}.")
55
65
  end
@@ -60,8 +70,8 @@ module GraphQL
60
70
  end
61
71
 
62
72
  # Add the shortcut `nodes` field to this connection and its subclasses
63
- def nodes_field(node_nullable: self.node_nullable)
64
- define_nodes_field(node_nullable)
73
+ def nodes_field(node_nullable: self.node_nullable, field_options: nil)
74
+ define_nodes_field(node_nullable, field_options: field_options)
65
75
  end
66
76
 
67
77
  def authorized?(obj, ctx)
@@ -118,11 +128,18 @@ module GraphQL
118
128
 
119
129
  private
120
130
 
121
- def define_nodes_field(nullable)
122
- field :nodes, [@node_type, null: nullable],
131
+ def define_nodes_field(nullable, field_options: nil)
132
+ base_field_options = {
133
+ name: :nodes,
134
+ type: [@node_type, null: nullable],
123
135
  null: nullable,
124
136
  description: "A list of nodes.",
125
- connection: false
137
+ connection: false,
138
+ }
139
+ if field_options
140
+ base_field_options.merge!(field_options)
141
+ end
142
+ field(**base_field_options)
126
143
  end
127
144
  end
128
145
 
@@ -17,7 +17,11 @@ module GraphQL
17
17
  end
18
18
 
19
19
  def to_graphql
20
- type_defn = super
20
+ type_defn = if method(:to_graphql).super_method.arity
21
+ super(silence_deprecation_warning: true)
22
+ else
23
+ super
24
+ end
21
25
  type_defn.default_relay = default_relay?
22
26
  type_defn
23
27
  end
@@ -16,11 +16,22 @@ module GraphQL
16
16
  #
17
17
  # @param node_type [Class] A `Schema::Object` subclass
18
18
  # @param null [Boolean]
19
- def node_type(node_type = nil, null: self.node_nullable)
19
+ # @param field_options [Hash] Any extra arguments to pass to the `field :node` configuration
20
+ def node_type(node_type = nil, null: self.node_nullable, field_options: nil)
20
21
  if node_type
21
22
  @node_type = node_type
22
23
  # Add a default `node` field
23
- field :node, node_type, null: null, description: "The item at the end of the edge.", connection: false
24
+ base_field_options = {
25
+ name: :node,
26
+ type: node_type,
27
+ null: null,
28
+ description: "The item at the end of the edge.",
29
+ connection: false,
30
+ }
31
+ if field_options
32
+ base_field_options.merge!(field_options)
33
+ end
34
+ field(**base_field_options)
24
35
  end
25
36
  @node_type
26
37
  end
@@ -22,7 +22,7 @@ module GraphQL
22
22
 
23
23
  def field_block
24
24
  Proc.new {
25
- argument :id, "ID!", required: true,
25
+ argument :id, "ID!",
26
26
  description: "ID of the object."
27
27
 
28
28
  def resolve(obj, args, ctx)
@@ -22,7 +22,7 @@ module GraphQL
22
22
 
23
23
  def field_block
24
24
  Proc.new {
25
- argument :ids, "[ID!]!", required: true,
25
+ argument :ids, "[ID!]!",
26
26
  description: "IDs of the objects."
27
27
 
28
28
  def resolve(obj, args, ctx)
@@ -15,7 +15,7 @@ module GraphQL
15
15
  str.encode!(Encoding::UTF_8)
16
16
  end
17
17
  rescue EncodingError
18
- err = GraphQL::StringEncodingError.new(str)
18
+ err = GraphQL::StringEncodingError.new(str, context: ctx)
19
19
  ctx.schema.type_error(err, ctx)
20
20
  end
21
21
 
@@ -12,7 +12,7 @@ module GraphQL
12
12
  attr_reader :type
13
13
 
14
14
  # @return [GraphQL::Query::Context] the context for the current query
15
- attr_reader :context
15
+ attr_accessor :context
16
16
 
17
17
  def initialize(message = nil, object: nil, type: nil, context: nil)
18
18
  if message.nil? && object.nil? && type.nil?
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.12.16"
3
+ VERSION = "1.13.2"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -55,31 +55,6 @@ module GraphQL
55
55
  def self.scan_with_ragel(graphql_string)
56
56
  GraphQL::Language::Lexer.tokenize(graphql_string)
57
57
  end
58
-
59
- # Support Ruby 2.2 by implementing `-"str"`. If we drop 2.2 support, we can remove this backport.
60
- if !String.method_defined?(:-@)
61
- module StringDedupBackport
62
- refine String do
63
- def -@
64
- if frozen?
65
- self
66
- else
67
- self.dup.freeze
68
- end
69
- end
70
- end
71
- end
72
- end
73
-
74
- if !String.method_defined?(:match?)
75
- module StringMatchBackport
76
- refine String do
77
- def match?(pattern)
78
- self =~ pattern
79
- end
80
- end
81
- end
82
- end
83
58
  end
84
59
 
85
60
  # Order matters for these:
@@ -125,10 +100,14 @@ require "graphql/execution"
125
100
  require "graphql/pagination"
126
101
  require "graphql/schema"
127
102
  require "graphql/query"
103
+ require "graphql/types"
104
+ require "graphql/dataloader"
105
+ require "graphql/filter"
106
+ require "graphql/internal_representation"
128
107
  require "graphql/directive"
108
+ require "graphql/static_validation"
129
109
  require "graphql/execution"
130
- require "graphql/types"
131
- require "graphql/relay"
110
+ require "graphql/deprecation"
132
111
  require "graphql/boolean_type"
133
112
  require "graphql/float_type"
134
113
  require "graphql/id_type"
@@ -137,11 +116,8 @@ require "graphql/string_type"
137
116
  require "graphql/schema/built_in_types"
138
117
  require "graphql/schema/loader"
139
118
  require "graphql/schema/printer"
140
- require "graphql/filter"
141
- require "graphql/internal_representation"
142
- require "graphql/static_validation"
143
- require "graphql/dataloader"
144
119
  require "graphql/introspection"
120
+ require "graphql/relay"
145
121
 
146
122
  require "graphql/version"
147
123
  require "graphql/compatibility"
@@ -155,7 +131,9 @@ require "graphql/authorization"
155
131
  require "graphql/unauthorized_error"
156
132
  require "graphql/unauthorized_field_error"
157
133
  require "graphql/load_application_object_failed_error"
158
- require "graphql/deprecation"
134
+ require "graphql/directive/include_directive"
135
+ require "graphql/directive/skip_directive"
136
+ require "graphql/directive/deprecated_directive"
159
137
 
160
138
  module GraphQL
161
139
  # Ruby has `deprecate_constant`,
data/readme.md CHANGED
@@ -44,6 +44,6 @@ I also sell [GraphQL::Pro](https://graphql.pro) which provides several features
44
44
 
45
45
  ## Getting Involved
46
46
 
47
- - __Say hi & ask questions__ in the [#ruby channel on Slack](https://graphql-slack.herokuapp.com/) or [on Twitter](https://twitter.com/rmosolgo)!
47
+ - __Say hi & ask questions__ in the #graphql-ruby channel on [Discord](https://discord.com/invite/xud7bH9) or [on Twitter](https://twitter.com/rmosolgo)!
48
48
  - __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues).
49
49
  - __Start hacking__ with the [Development guide](https://graphql-ruby.org/development).