Class: GraphQL::Schema::Argument

Inherits:
Object
  • Object
show all
Includes:
EmptyObjects, Member::HasAstNode, Member::HasAuthorization, Member::HasDeprecationReason, Member::HasDirectives, Member::HasPath, Member::HasValidators
Defined in:
lib/graphql/schema/argument.rb

Defined Under Namespace

Classes: InvalidDefaultValueError

Constant Summary

Constants included from EmptyObjects

EmptyObjects::EMPTY_ARRAY, EmptyObjects::EMPTY_HASH

Instance Attribute Summary collapse

Attributes included from Member::HasAstNode

#ast_node

Instance Method Summary collapse

Methods included from Member::HasValidators

#validates, #validators

Methods included from Member::HasDirectives

add_directive, #directive, #directives, get_directives, #inherited, #remove_directive, remove_directive

Methods included from Member::HasAstNode

#inherited

Methods included from Member::HasPath

#path

Constructor Details

#initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block) ⇒ Argument

Returns a new instance of Argument.

Parameters:

  • arg_name (Symbol) (defaults to: nil)
  • type_expr (defaults to: nil)
  • desc (String) (defaults to: nil)
  • type (Class, Array<Class>) (defaults to: nil)

    Input type; positional argument also accepted

  • name (Symbol) (defaults to: nil)

    positional argument also accepted # @param loads [Class, Array] A GraphQL type to load for the given ID when one is present

  • definition_block (Proc)

    Called with the newly-created GraphQL::Schema::Argument

  • owner (Class)

    Private, used by GraphQL-Ruby during schema definition

  • required (Boolean, :nullable) (defaults to: true)

    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.

  • description (String) (defaults to: nil)
  • default_value (Object) (defaults to: NOT_CONFIGURED)
  • loads (Class, Array<Class>) (defaults to: nil)

    A GraphQL type to load for the given ID when one is present

  • as (Symbol) (defaults to: nil)

    Override the keyword name when passed to a method

  • prepare (Symbol) (defaults to: nil)

    A method to call to transform this argument’s valuebefore sending it to field resolution

  • camelize (Boolean) (defaults to: true)

    if true, the name will be camelized when building the schema

  • from_resolver (Boolean) (defaults to: false)

    if true, a Resolver class defined this argument

  • directives (Hash{Class => Hash}) (defaults to: nil)
  • deprecation_reason (String) (defaults to: nil)
  • validates (Hash, nil) (defaults to: nil)

    Options for building validators, if any should be applied

  • replace_null_with_default (Boolean) (defaults to: false)

    if true, incoming values of null will be replaced with the configured default_value

  • comment (String) (defaults to: nil)

    Private, used by GraphQL-Ruby when parsing GraphQL schema files

  • ast_node (GraphQL::Language::Nodes::InputValueDefinition) (defaults to: nil)

    Private, used by GraphQL-Ruby when parsing schema files



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/graphql/schema/argument.rb', line 61

def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
  arg_name ||= name
  @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
  NameValidator.validate!(@name)
  @type_expr = type_expr || type
  @description = desc || description
  @comment = comment
  @null = required != true
  @default_value = default_value
  if replace_null_with_default
    if !default_value?
      raise ArgumentError, "`replace_null_with_default: true` requires a default value, please provide one with `default_value: ...`"
    end
    @replace_null_with_default = true
  end

  @owner = owner
  @as = as
  @loads = loads
  @keyword = as || (arg_name.is_a?(Symbol) ? arg_name : Schema::Member::BuildType.underscore(@name).to_sym)
  @prepare = prepare
  @ast_node = ast_node
  @from_resolver = from_resolver
  self.deprecation_reason = deprecation_reason

  if directives
    directives.each do |dir_class, dir_options|
      directive(dir_class, **dir_options)
    end
  end

  if validates && !validates.empty?
    self.validates(validates)
  end

  if required == :nullable
    self.owner.validates(required: { argument: arg_name })
  end

  if definition_block
    # `self` will still be self, it will also be the first argument to the block:
    instance_exec(self, &definition_block)
  end
end

Instance Attribute Details

#comment(text = nil) ⇒ String

Returns Comment for this argument.

Returns:

  • (String)

    Comment for this argument



142
143
144
145
146
147
148
# File 'lib/graphql/schema/argument.rb', line 142

def comment(text = nil)
  if text
    @comment = text
  else
    @comment
  end
end

#description(text = nil) ⇒ String

Returns Documentation for this argument.

Returns:

  • (String)

    Documentation for this argument



131
132
133
134
135
136
137
# File 'lib/graphql/schema/argument.rb', line 131

def description(text = nil)
  if text
    @description = text
  else
    @description
  end
end

#keywordSymbol (readonly)

Returns This argument’s name in Ruby keyword arguments.

Returns:

  • (Symbol)

    This argument’s name in Ruby keyword arguments



30
31
32
# File 'lib/graphql/schema/argument.rb', line 30

def keyword
  @keyword
end

#loadsClass, ... (readonly)

Returns If this argument should load an application object, this is the type of object to load.

Returns:

  • (Class, Module, nil)

    If this argument should load an application object, this is the type of object to load



33
34
35
# File 'lib/graphql/schema/argument.rb', line 33

def loads
  @loads
end

#nameString (readonly) Also known as: graphql_name

Returns the GraphQL name for this argument, camelized unless camelize: false is provided.

Returns:

  • (String)

    the GraphQL name for this argument, camelized unless camelize: false is provided



14
15
16
# File 'lib/graphql/schema/argument.rb', line 14

def name
  @name
end

#ownerGraphQL::Schema::Field, Class (readonly)

Returns The field or input object this argument belongs to.

Returns:



18
19
20
# File 'lib/graphql/schema/argument.rb', line 18

def owner
  @owner
end

Instance Method Details

#authorized?(obj, value, ctx) ⇒ Boolean

Returns:

  • (Boolean)


172
173
174
# File 'lib/graphql/schema/argument.rb', line 172

def authorized?(obj, value, ctx)
  authorized_as_type?(obj, value, ctx, as_type: type)
end

#authorized_as_type?(obj, value, ctx, as_type:) ⇒ Boolean

Returns:

  • (Boolean)


176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/graphql/schema/argument.rb', line 176

def authorized_as_type?(obj, value, ctx, as_type:)
  if value.nil?
    return true
  end

  if as_type.kind.non_null?
    as_type = as_type.of_type
  end

  if as_type.kind.list?
    value.each do |v|
      if !authorized_as_type?(obj, v, ctx, as_type: as_type.of_type)
        return false
      end
    end
  elsif as_type.kind.input_object?
    return as_type.authorized?(obj, value, ctx)
  end
  # None of the early-return conditions were activated,
  # so this is authorized.
  true
end

#authorizes?(_context) ⇒ Boolean

Returns:

  • (Boolean)


168
169
170
# File 'lib/graphql/schema/argument.rb', line 168

def authorizes?(_context)
  self.method(:authorized?).owner != GraphQL::Schema::Argument
end

#coerce_into_values(parent_object, values, context, argument_values) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/graphql/schema/argument.rb', line 267

def coerce_into_values(parent_object, values, context, argument_values)
  arg_name = graphql_name
  arg_key = keyword
  default_used = false

  if values.key?(arg_name)
    value = values[arg_name]
  elsif values.key?(arg_key)
    value = values[arg_key]
  elsif default_value?
    value = default_value
    default_used = true
  else
    # no value at all
    owner.validate_directive_argument(self, nil)
    return
  end

  if value.nil? && replace_null_with_default?
    value = default_value
    default_used = true
  end

  loaded_value = nil
  coerced_value = begin
    type.coerce_input(value, context)
  rescue StandardError => err
    context.schema.handle_or_reraise(context, err)
  end

  # If this isn't lazy, then the block returns eagerly and assigns the result here
  # If it _is_ lazy, then we write the lazy to the hash, then update it later
  argument_values[arg_key] = context.query.after_lazy(coerced_value) do |resolved_coerced_value|
    owner.validate_directive_argument(self, resolved_coerced_value)
    prepared_value = begin
      prepare_value(parent_object, resolved_coerced_value, context: context)
    rescue StandardError => err
      context.schema.handle_or_reraise(context, err)
    end

    if loads && !from_resolver?
      loaded_value = begin
        load_and_authorize_value(owner, prepared_value, context)
      rescue StandardError => err
        context.schema.handle_or_reraise(context, err)
      end
    end

    maybe_loaded_value = loaded_value || prepared_value
    context.query.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
      # TODO code smell to access such a deeply-nested constant in a distant module
      argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
        value: resolved_loaded_value,
        original_value: resolved_coerced_value,
        definition: self,
        default_used: default_used,
      )
    end
  end
end

#default_value(new_default_value = NOT_CONFIGURED) ⇒ Object

Returns the value used when the client doesn’t provide a value for this argument.

Parameters:

  • default_value (Object)

    The value to use when the client doesn’t provide one

Returns:

  • (Object)

    the value used when the client doesn’t provide a value for this argument



112
113
114
115
116
117
# File 'lib/graphql/schema/argument.rb', line 112

def default_value(new_default_value = NOT_CONFIGURED)
  if new_default_value != NOT_CONFIGURED
    @default_value = new_default_value
  end
  @default_value
end

#default_value?Boolean

Returns True if this argument has a default value.

Returns:

  • (Boolean)

    True if this argument has a default value



120
121
122
# File 'lib/graphql/schema/argument.rb', line 120

def default_value?
  @default_value != NOT_CONFIGURED
end

#deprecation_reason(text = nil) ⇒ String

Returns Deprecation reason for this argument.

Returns:

  • (String)

    Deprecation reason for this argument



151
152
153
154
155
156
157
# File 'lib/graphql/schema/argument.rb', line 151

def deprecation_reason(text = nil)
  if text
    self.deprecation_reason = text
  else
    super()
  end
end

#deprecation_reason=(new_reason) ⇒ Object



159
160
161
162
# File 'lib/graphql/schema/argument.rb', line 159

def deprecation_reason=(new_reason)
  validate_deprecated_or_optional(null: @null, deprecation_reason: new_reason)
  super
end

#freezeObject



227
228
229
230
# File 'lib/graphql/schema/argument.rb', line 227

def freeze
  statically_coercible?
  super
end

#from_resolver?Boolean

Returns true if a resolver defined this argument.

Returns:

  • (Boolean)

    true if a resolver defined this argument



36
37
38
# File 'lib/graphql/schema/argument.rb', line 36

def from_resolver?
  @from_resolver
end

#inspectObject



106
107
108
# File 'lib/graphql/schema/argument.rb', line 106

def inspect
  "#<#{self.class} #{path}: #{type.to_type_signature}#{description ? " @description=#{description.inspect}" : ""}>"
end

#load_and_authorize_value(load_method_owner, coerced_value, context) ⇒ Object



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/graphql/schema/argument.rb', line 328

def load_and_authorize_value(load_method_owner, coerced_value, context)
  if coerced_value.nil?
    return nil
  end
  arg_load_method = "load_#{keyword}"
  if load_method_owner.respond_to?(arg_load_method)
    custom_loaded_value = if load_method_owner.is_a?(Class)
      load_method_owner.public_send(arg_load_method, coerced_value, context)
    else
      load_method_owner.public_send(arg_load_method, coerced_value)
    end
    context.query.after_lazy(custom_loaded_value) do |custom_value|
      if loads
        if type.list?
          loaded_values = []
          context.dataloader.run_isolated do
            custom_value.each_with_index.map { |custom_val, idx|
              id = coerced_value[idx]
              context.dataloader.append_job do
                loaded_values[idx] = load_method_owner.authorize_application_object(self, id, context, custom_val)
              end
            }
          end
          context.schema.after_any_lazies(loaded_values, &:itself)
        else
          load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
        end
      else
        custom_value
      end
    end
  elsif loads
    if type.list?
      loaded_values = []
      # We want to run these list items all together,
      # but we also need to wait for the result so we can return it :S
      context.dataloader.run_isolated do
        coerced_value.each_with_index { |val, idx|
          context.dataloader.append_job do
            loaded_values[idx] = load_method_owner.load_and_authorize_application_object(self, val, context)
          end
        }
      end
      context.schema.after_any_lazies(loaded_values, &:itself)
    else
      load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
    end
  else
    coerced_value
  end
end

#prepare(new_prepare = NOT_CONFIGURED) ⇒ Symbol

Returns A method or proc to call to transform this value before sending it to field resolution method.

Parameters:

  • new_prepare (Method, Proc) (defaults to: NOT_CONFIGURED)

Returns:

  • (Symbol)

    A method or proc to call to transform this value before sending it to field resolution method



22
23
24
25
26
27
# File 'lib/graphql/schema/argument.rb', line 22

def prepare(new_prepare = NOT_CONFIGURED)
  if new_prepare != NOT_CONFIGURED
    @prepare = new_prepare
  end
  @prepare
end

#prepare_value(obj, value, context: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Apply the #prepare configuration to value, using methods from obj. Used by the runtime.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/graphql/schema/argument.rb', line 235

def prepare_value(obj, value, context: nil)
  if type.unwrap.kind.input_object?
    value = recursively_prepare_input_object(value, type, context)
  end

  Schema::Validator.validate!(validators, obj, context, value)

  if @prepare.nil?
    value
  elsif @prepare.is_a?(String) || @prepare.is_a?(Symbol)
    if obj.nil?
      # The problem here is, we _used to_ prepare while building variables.
      # But now we don't have the runtime object there.
      #
      # This will have to be called later, when the runtime object _is_ available.
      value
    elsif obj.respond_to?(@prepare)
      obj.public_send(@prepare, value)
    elsif owner.respond_to?(@prepare)
      owner.public_send(@prepare, value, context || obj.context)
    else
      raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}. "\
        "Could not find prepare method #{@prepare} on #{obj.class} or #{owner}."
    end
  elsif @prepare.respond_to?(:call)
    @prepare.call(value, context || obj.context)
  else
    raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}"
  end
end

#replace_null_with_default?Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/graphql/schema/argument.rb', line 124

def replace_null_with_default?
  @replace_null_with_default
end

#statically_coercible?Boolean

Returns:

  • (Boolean)


221
222
223
224
225
# File 'lib/graphql/schema/argument.rb', line 221

def statically_coercible?
  return @statically_coercible if defined?(@statically_coercible)
  requires_parent_object = @prepare.is_a?(String) || @prepare.is_a?(Symbol) || @own_validators
  @statically_coercible = !requires_parent_object
end

#typeObject



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/graphql/schema/argument.rb', line 209

def type
  @type ||= begin
    parsed_type = begin
      Member::BuildType.parse_type(@type_expr, null: @null)
    rescue StandardError => err
      raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace
    end
    # Use the setter method to get validations
    self.type = parsed_type
  end
end

#type=(new_type) ⇒ Object



199
200
201
202
203
204
205
206
207
# File 'lib/graphql/schema/argument.rb', line 199

def type=(new_type)
  validate_input_type(new_type)
  # This isn't true for LateBoundTypes, but we can assume those will
  # be updated via this codepath later in schema setup.
  if new_type.respond_to?(:non_null?)
    validate_deprecated_or_optional(null: !new_type.non_null?, deprecation_reason: deprecation_reason)
  end
  @type = new_type
end

#validate_default_valueObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/graphql/schema/argument.rb', line 381

def validate_default_value
  return unless default_value?
  coerced_default_value = begin
    # This is weird, but we should accept single-item default values for list-type arguments.
    # If we used `coerce_isolated_input` below, it would do this for us, but it's not really
    # the right thing here because we expect default values in application format (Ruby values)
    # not GraphQL format (scalar values).
    #
    # But I don't think Schema::List#coerce_result should apply wrapping to single-item lists.
    prepped_default_value = if default_value.nil?
      nil
    elsif (type.kind.list? || (type.kind.non_null? && type.of_type.list?)) && !default_value.respond_to?(:map)
      [default_value]
    else
      default_value
    end

    type.coerce_isolated_result(prepped_default_value) unless prepped_default_value.nil?
  rescue GraphQL::Schema::Enum::UnresolvedValueError
    # It raises this, which is helpful at runtime, but not here...
    default_value
  end
  res = type.valid_isolated_input?(coerced_default_value)
  if !res
    raise InvalidDefaultValueError.new(self)
  end
end

#visible?(context) ⇒ Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/graphql/schema/argument.rb', line 164

def visible?(context)
  true
end