graphql 1.10.12 → 1.11.2

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/graphql_controller.erb +11 -9
  3. data/lib/graphql.rb +3 -3
  4. data/lib/graphql/directive.rb +4 -0
  5. data/lib/graphql/execution/interpreter.rb +1 -1
  6. data/lib/graphql/execution/interpreter/runtime.rb +6 -4
  7. data/lib/graphql/execution/multiplex.rb +1 -2
  8. data/lib/graphql/field.rb +4 -0
  9. data/lib/graphql/input_object_type.rb +4 -0
  10. data/lib/graphql/introspection/schema_type.rb +3 -3
  11. data/lib/graphql/invalid_null_error.rb +18 -0
  12. data/lib/graphql/language/nodes.rb +1 -0
  13. data/lib/graphql/language/visitor.rb +2 -2
  14. data/lib/graphql/pagination/connection.rb +18 -13
  15. data/lib/graphql/pagination/connections.rb +17 -4
  16. data/lib/graphql/query.rb +1 -2
  17. data/lib/graphql/schema.rb +22 -16
  18. data/lib/graphql/schema/argument.rb +5 -0
  19. data/lib/graphql/schema/build_from_definition.rb +7 -12
  20. data/lib/graphql/schema/enum_value.rb +1 -0
  21. data/lib/graphql/schema/field.rb +63 -77
  22. data/lib/graphql/schema/field/connection_extension.rb +5 -1
  23. data/lib/graphql/schema/input_object.rb +16 -15
  24. data/lib/graphql/schema/loader.rb +19 -1
  25. data/lib/graphql/schema/member/has_arguments.rb +16 -0
  26. data/lib/graphql/schema/object.rb +1 -1
  27. data/lib/graphql/schema/resolver.rb +14 -0
  28. data/lib/graphql/schema/subscription.rb +1 -1
  29. data/lib/graphql/schema/union.rb +29 -0
  30. data/lib/graphql/schema/warden.rb +6 -1
  31. data/lib/graphql/static_validation/literal_validator.rb +7 -7
  32. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  33. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  34. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -2
  35. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  36. data/lib/graphql/subscriptions.rb +41 -8
  37. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +66 -11
  38. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  39. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  40. data/lib/graphql/subscriptions/event.rb +16 -1
  41. data/lib/graphql/subscriptions/serialize.rb +22 -4
  42. data/lib/graphql/subscriptions/subscription_root.rb +3 -1
  43. data/lib/graphql/tracing.rb +1 -27
  44. data/lib/graphql/tracing/platform_tracing.rb +25 -15
  45. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  46. data/lib/graphql/version.rb +1 -1
  47. metadata +5 -2
@@ -43,9 +43,7 @@ module GraphQL
43
43
  when GraphQL::Language::Nodes::EnumTypeDefinition
44
44
  types[definition.name] = build_enum_type(definition, type_resolver)
45
45
  when GraphQL::Language::Nodes::ObjectTypeDefinition
46
- is_subscription_root = (definition.name == "Subscription" && (schema_definition.nil? || schema_definition.subscription.nil?)) || (schema_definition && (definition.name == schema_definition.subscription))
47
- should_extend_subscription_root = is_subscription_root && interpreter
48
- types[definition.name] = build_object_type(definition, type_resolver, default_resolve: default_resolve, extend_subscription_root: should_extend_subscription_root)
46
+ types[definition.name] = build_object_type(definition, type_resolver, default_resolve: default_resolve)
49
47
  when GraphQL::Language::Nodes::InterfaceTypeDefinition
50
48
  types[definition.name] = build_interface_type(definition, type_resolver)
51
49
  when GraphQL::Language::Nodes::UnionTypeDefinition
@@ -204,7 +202,7 @@ module GraphQL
204
202
  end
205
203
  end
206
204
 
207
- def build_object_type(object_type_definition, type_resolver, default_resolve:, extend_subscription_root:)
205
+ def build_object_type(object_type_definition, type_resolver, default_resolve:)
208
206
  builder = self
209
207
  type_def = nil
210
208
  typed_resolve_fn = ->(field, obj, args, ctx) { default_resolve.call(type_def, field, obj, args, ctx) }
@@ -214,10 +212,6 @@ module GraphQL
214
212
  graphql_name(object_type_definition.name)
215
213
  description(object_type_definition.description)
216
214
  ast_node(object_type_definition)
217
- if extend_subscription_root
218
- # This has to come before `field ...` configurations since it modifies them
219
- extend Subscriptions::SubscriptionRoot
220
- end
221
215
 
222
216
  object_type_definition.interfaces.each do |interface_name|
223
217
  interface_defn = type_resolver.call(interface_name)
@@ -303,7 +297,7 @@ module GraphQL
303
297
 
304
298
  field_definitions.map do |field_definition|
305
299
  type_name = resolve_type_name(field_definition.type)
306
-
300
+ resolve_method_name = "resolve_field_#{field_definition.name}"
307
301
  owner.field(
308
302
  field_definition.name,
309
303
  description: field_definition.description,
@@ -315,14 +309,15 @@ module GraphQL
315
309
  ast_node: field_definition,
316
310
  method_conflict_warning: false,
317
311
  camelize: false,
312
+ resolver_method: resolve_method_name,
318
313
  ) do
319
314
  builder.build_arguments(self, field_definition.arguments, type_resolver)
320
315
 
321
316
  # Don't do this for interfaces
322
317
  if default_resolve
323
- # TODO fragile hack. formalize this API?
324
- define_singleton_method :resolve_field_method do |obj, args, ctx|
325
- default_resolve.call(self, obj.object, args, ctx)
318
+ owner.send(:define_method, resolve_method_name) do |**args|
319
+ field_instance = self.class.get_field(field_definition.name)
320
+ default_resolve.call(field_instance, object, args, context)
326
321
  end
327
322
  end
328
323
  end
@@ -41,6 +41,7 @@ module GraphQL
41
41
 
42
42
  def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil, value: nil, deprecation_reason: nil, &block)
43
43
  @graphql_name = graphql_name.to_s
44
+ GraphQL::NameValidator.validate!(@graphql_name)
44
45
  @description = desc || description
45
46
  @value = value.nil? ? @graphql_name : value
46
47
  @deprecation_reason = deprecation_reason
@@ -36,9 +36,18 @@ module GraphQL
36
36
  # @return [Symbol] The method on the type to look up
37
37
  attr_reader :resolver_method
38
38
 
39
- # @return [Class] The type that this field belongs to
39
+ # @return [Class] The thing this field was defined on (type, mutation, resolver)
40
40
  attr_accessor :owner
41
41
 
42
+ # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
43
+ def owner_type
44
+ @owner_type ||= if owner < GraphQL::Schema::Mutation
45
+ owner.payload_type
46
+ else
47
+ owner
48
+ end
49
+ end
50
+
42
51
  # @return [Symbol] the original name of the field, passed in by the user
43
52
  attr_reader :original_name
44
53
 
@@ -191,9 +200,10 @@ module GraphQL
191
200
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
192
201
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
193
202
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
203
+ # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
194
204
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
195
205
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
196
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
206
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block)
197
207
  if name.nil?
198
208
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
199
209
  end
@@ -237,8 +247,8 @@ module GraphQL
237
247
  end
238
248
 
239
249
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
240
- method_name = method || hash_key || @underscored_name
241
- resolver_method ||= @underscored_name.to_sym
250
+ method_name = method || hash_key || name_s
251
+ resolver_method ||= name_s.to_sym
242
252
 
243
253
  @method_str = method_name.to_s
244
254
  @method_sym = method_name.to_sym
@@ -251,6 +261,7 @@ module GraphQL
251
261
  @max_page_size = max_page_size == :not_given ? nil : max_page_size
252
262
  @introspection = introspection
253
263
  @extras = extras
264
+ @broadcastable = broadcastable
254
265
  @resolver_class = resolver_class
255
266
  @scope = scope
256
267
  @trace = trace
@@ -295,6 +306,13 @@ module GraphQL
295
306
  end
296
307
  end
297
308
 
309
+ # If true, subscription updates with this field can be shared between viewers
310
+ # @return [Boolean, nil]
311
+ # @see GraphQL::Subscriptions::BroadcastAnalyzer
312
+ def broadcastable?
313
+ @broadcastable
314
+ end
315
+
298
316
  # @param text [String]
299
317
  # @return [String]
300
318
  def description(text = nil)
@@ -534,7 +552,7 @@ module GraphQL
534
552
  @resolve_proc.call(extended_obj, args, ctx)
535
553
  end
536
554
  else
537
- public_send_field(after_obj, ruby_args, ctx)
555
+ public_send_field(after_obj, ruby_args, query_ctx)
538
556
  end
539
557
  else
540
558
  err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
@@ -558,30 +576,7 @@ module GraphQL
558
576
  application_object = object.object
559
577
  ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
560
578
  if is_authorized
561
- # Apply field extensions
562
- with_extensions(object, args, ctx) do |extended_obj, extended_args|
563
- field_receiver = if @resolver_class
564
- resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
565
- extended_obj.object
566
- else
567
- extended_obj
568
- end
569
- @resolver_class.new(object: resolver_obj, context: ctx, field: self)
570
- else
571
- extended_obj
572
- end
573
-
574
- if field_receiver.respond_to?(@resolver_method)
575
- # Call the method with kwargs, if there are any
576
- if extended_args.any?
577
- field_receiver.public_send(@resolver_method, **extended_args)
578
- else
579
- field_receiver.public_send(@resolver_method)
580
- end
581
- else
582
- resolve_field_method(field_receiver, extended_args, ctx)
583
- end
584
- end
579
+ public_send_field(object, args, ctx)
585
580
  else
586
581
  err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
587
582
  ctx.schema.unauthorized_field(err)
@@ -597,43 +592,6 @@ module GraphQL
597
592
  err
598
593
  end
599
594
 
600
- # Find a way to resolve this field, checking:
601
- #
602
- # - Hash keys, if the wrapped object is a hash;
603
- # - A method on the wrapped object;
604
- # - Or, raise not implemented.
605
- #
606
- # This can be overridden by defining a method on the object type.
607
- # @param obj [GraphQL::Schema::Object]
608
- # @param ruby_kwargs [Hash<Symbol => Object>]
609
- # @param ctx [GraphQL::Query::Context]
610
- def resolve_field_method(obj, ruby_kwargs, ctx)
611
- if obj.object.is_a?(Hash)
612
- inner_object = obj.object
613
- if inner_object.key?(@method_sym)
614
- inner_object[@method_sym]
615
- else
616
- inner_object[@method_str]
617
- end
618
- elsif obj.object.respond_to?(@method_sym)
619
- if ruby_kwargs.any?
620
- obj.object.public_send(@method_sym, **ruby_kwargs)
621
- else
622
- obj.object.public_send(@method_sym)
623
- end
624
- else
625
- raise <<-ERR
626
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
627
-
628
- - `#{obj.class}##{@resolver_method}`, which did not exist
629
- - `#{obj.object.class}##{@method_sym}`, which did not exist
630
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
631
-
632
- To implement this field, define one of the methods above (and check for typos)
633
- ERR
634
- end
635
- end
636
-
637
595
  # @param ctx [GraphQL::Query::Context::FieldResolutionContext]
638
596
  def fetch_extra(extra_name, ctx)
639
597
  if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name)
@@ -704,24 +662,52 @@ module GraphQL
704
662
  end
705
663
  end
706
664
 
707
- def public_send_field(obj, ruby_kwargs, field_ctx)
708
- query_ctx = field_ctx.query.context
709
- with_extensions(obj, ruby_kwargs, query_ctx) do |extended_obj, extended_args|
665
+ def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
666
+ with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
710
667
  if @resolver_class
711
- if extended_obj.is_a?(GraphQL::Schema::Object)
712
- extended_obj = extended_obj.object
668
+ if obj.is_a?(GraphQL::Schema::Object)
669
+ obj = obj.object
713
670
  end
714
- extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx, field: self)
671
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
715
672
  end
716
673
 
717
- if extended_obj.respond_to?(@resolver_method)
718
- if extended_args.any?
719
- extended_obj.public_send(@resolver_method, **extended_args)
674
+ # Find a way to resolve this field, checking:
675
+ #
676
+ # - A method on the type instance;
677
+ # - Hash keys, if the wrapped object is a hash;
678
+ # - A method on the wrapped object;
679
+ # - Or, raise not implemented.
680
+ #
681
+ if obj.respond_to?(@resolver_method)
682
+ # Call the method with kwargs, if there are any
683
+ if ruby_kwargs.any?
684
+ obj.public_send(@resolver_method, **ruby_kwargs)
685
+ else
686
+ obj.public_send(@resolver_method)
687
+ end
688
+ elsif obj.object.is_a?(Hash)
689
+ inner_object = obj.object
690
+ if inner_object.key?(@method_sym)
691
+ inner_object[@method_sym]
720
692
  else
721
- extended_obj.public_send(@resolver_method)
693
+ inner_object[@method_str]
694
+ end
695
+ elsif obj.object.respond_to?(@method_sym)
696
+ if ruby_kwargs.any?
697
+ obj.object.public_send(@method_sym, **ruby_kwargs)
698
+ else
699
+ obj.object.public_send(@method_sym)
722
700
  end
723
701
  else
724
- resolve_field_method(extended_obj, extended_args, query_ctx)
702
+ raise <<-ERR
703
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
704
+
705
+ - `#{obj.class}##{@resolver_method}`, which did not exist
706
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
707
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
708
+
709
+ To implement this field, define one of the methods above (and check for typos)
710
+ ERR
725
711
  end
726
712
  end
727
713
  end
@@ -31,6 +31,7 @@ module GraphQL
31
31
  elsif value.is_a?(GraphQL::Pagination::Connection)
32
32
  # update the connection with some things that may not have been provided
33
33
  value.context ||= context
34
+ value.parent ||= object.object
34
35
  value.first_value ||= arguments[:first]
35
36
  value.after_value ||= arguments[:after]
36
37
  value.last_value ||= arguments[:last]
@@ -38,10 +39,13 @@ module GraphQL
38
39
  if field.has_max_page_size? && !value.has_max_page_size_override?
39
40
  value.max_page_size = field.max_page_size
40
41
  end
42
+ if (custom_t = context.schema.connections.edge_class_for_field(@field))
43
+ value.edge_class = custom_t
44
+ end
41
45
  value
42
46
  elsif context.schema.new_connections?
43
47
  wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
44
- context.schema.connections.wrap(field, value, arguments, context, wrappers: wrappers)
48
+ context.schema.connections.wrap(field, object.object, value, arguments, context, wrappers: wrappers)
45
49
  else
46
50
  if object.is_a?(GraphQL::Schema::Object)
47
51
  object = object.object
@@ -166,10 +166,7 @@ module GraphQL
166
166
  return result
167
167
  end
168
168
 
169
- # We're not actually _using_ the coerced result, we're just
170
- # using these methods to make sure that the object will
171
- # behave like a hash below, when we call `each` on it.
172
- begin
169
+ input = begin
173
170
  input.to_h
174
171
  rescue
175
172
  begin
@@ -182,21 +179,25 @@ module GraphQL
182
179
  end
183
180
  end
184
181
 
185
- visible_arguments_map = warden.arguments(self).reduce({}) { |m, f| m[f.name] = f; m}
186
-
187
- # Items in the input that are unexpected
188
- input.each do |name, value|
189
- if visible_arguments_map[name].nil?
190
- result.add_problem("Field is not defined on #{self.graphql_name}", [name])
182
+ # Inject missing required arguments
183
+ missing_required_inputs = self.arguments.reduce({}) do |m, (argument_name, argument)|
184
+ if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name)
185
+ m[argument_name] = nil
191
186
  end
187
+
188
+ m
192
189
  end
193
190
 
194
- # Items in the input that are expected, but have invalid values
195
- visible_arguments_map.map do |name, argument|
196
- argument_result = argument.type.validate_input(input[name], ctx)
197
- if !argument_result.valid?
198
- result.merge_result!(name, argument_result)
191
+ input.merge(missing_required_inputs).each do |argument_name, value|
192
+ argument = warden.get_argument(self, argument_name)
193
+ # Items in the input that are unexpected
194
+ unless argument
195
+ result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
196
+ next
199
197
  end
198
+ # Items in the input that are expected, but have invalid values
199
+ argument_result = argument.type.validate_input(value, ctx)
200
+ result.merge_result!(argument_name, argument_result) unless argument_result.valid?
200
201
  end
201
202
 
202
203
  result
@@ -25,8 +25,15 @@ module GraphQL
25
25
  types[type["name"]] = type_object
26
26
  end
27
27
 
28
+ directives = []
29
+ schema.fetch("directives", []).each do |directive|
30
+ next if GraphQL::Schema.default_directives.include?(directive.fetch("name"))
31
+ directives << define_directive(directive, type_resolver)
32
+ end
33
+
28
34
  Class.new(GraphQL::Schema) do
29
35
  orphan_types(types.values)
36
+ directives(directives)
30
37
 
31
38
  def self.resolve_type(*)
32
39
  raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
@@ -98,7 +105,7 @@ module GraphQL
98
105
  value(
99
106
  enum_value["name"],
100
107
  description: enum_value["description"],
101
- deprecation_reason: enum_value["deprecation_reason"],
108
+ deprecation_reason: enum_value["deprecationReason"],
102
109
  )
103
110
  end
104
111
  end
@@ -147,6 +154,16 @@ module GraphQL
147
154
  end
148
155
  end
149
156
 
157
+ def define_directive(directive, type_resolver)
158
+ loader = self
159
+ Class.new(GraphQL::Schema::Directive) do
160
+ graphql_name(directive["name"])
161
+ description(directive["description"])
162
+ locations(*directive["locations"].map(&:to_sym))
163
+ loader.build_arguments(self, directive["args"], type_resolver)
164
+ end
165
+ end
166
+
150
167
  public
151
168
 
152
169
  def build_fields(type_defn, fields, type_resolver)
@@ -156,6 +173,7 @@ module GraphQL
156
173
  field_hash["name"],
157
174
  type: type_resolver.call(field_hash["type"]),
158
175
  description: field_hash["description"],
176
+ deprecation_reason: field_hash["deprecationReason"],
159
177
  null: true,
160
178
  camelize: false,
161
179
  ) do
@@ -58,6 +58,22 @@ module GraphQL
58
58
  end
59
59
  end
60
60
 
61
+ # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
62
+ def get_argument(argument_name)
63
+ a = own_arguments[argument_name]
64
+
65
+ if a || !self.is_a?(Class)
66
+ a
67
+ else
68
+ for ancestor in ancestors
69
+ if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name]
70
+ return a
71
+ end
72
+ end
73
+ nil
74
+ end
75
+ end
76
+
61
77
  # @param new_arg_class [Class] A class to use for building argument definitions
62
78
  def argument_class(new_arg_class = nil)
63
79
  self.class.argument_class(new_arg_class)
@@ -74,7 +74,7 @@ module GraphQL
74
74
  # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
75
75
  # It should help with debugging and bug tracker integrations.
76
76
  def inherited(child_class)
77
- child_class.const_set(:InvalidNullError, Class.new(GraphQL::InvalidNullError))
77
+ child_class.const_set(:InvalidNullError, GraphQL::InvalidNullError.subclass_for(child_class))
78
78
  super
79
79
  end
80
80
 
@@ -250,6 +250,19 @@ module GraphQL
250
250
  @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
251
251
  end
252
252
 
253
+ def broadcastable(new_broadcastable)
254
+ @broadcastable = new_broadcastable
255
+ end
256
+
257
+ # @return [Boolean, nil]
258
+ def broadcastable?
259
+ if defined?(@broadcastable)
260
+ @broadcastable
261
+ else
262
+ (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil)
263
+ end
264
+ end
265
+
253
266
  def field_options
254
267
  {
255
268
  type: type_expr,
@@ -261,6 +274,7 @@ module GraphQL
261
274
  null: null,
262
275
  complexity: complexity,
263
276
  extensions: extensions,
277
+ broadcastable: broadcastable?,
264
278
  }
265
279
  end
266
280