Class: GraphQL::Schema::Argument

Inherits:
Object
  • Object
show all
Includes:
EmptyObjects, Member::HasAstNode, 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, 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)
  • 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)
  • 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



53
54
55
56
57
58
59
60
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
# File 'lib/graphql/schema/argument.rb', line 53

def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: 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)
  @type_expr = type_expr || type
  @description = desc || description
  @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
    if definition_block.arity == 1
      instance_exec(self, &definition_block)
    else
      instance_eval(&definition_block)
    end
  end
end

Instance Attribute Details

#description(text = nil) ⇒ String

Returns Documentation for this argument.

Returns:

  • (String)

    Documentation for this argument



124
125
126
127
128
129
130
# File 'lib/graphql/schema/argument.rb', line 124

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



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

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



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

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



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

def name
  @name
end

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

Returns The field or input object this argument belongs to.

Returns:



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

def owner
  @owner
end

Instance Method Details

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

Returns:

  • (Boolean)


150
151
152
# File 'lib/graphql/schema/argument.rb', line 150

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)


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/graphql/schema/argument.rb', line 154

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

#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.



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
265
266
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
# File 'lib/graphql/schema/argument.rb', line 240

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



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

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



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

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



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

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

#deprecation_reason=(new_reason) ⇒ Object



141
142
143
144
# File 'lib/graphql/schema/argument.rb', line 141

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

#from_resolver?Boolean

Returns true if a resolver defined this argument.

Returns:

  • (Boolean)

    true if a resolver defined this argument



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

def from_resolver?
  @from_resolver
end

#inspectObject



99
100
101
# File 'lib/graphql/schema/argument.rb', line 99

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



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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/graphql/schema/argument.rb', line 301

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



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

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.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/graphql/schema/argument.rb', line 208

def prepare_value(obj, value, context: nil)
  if type.unwrap.kind.input_object?
    value = recursively_prepare_input_object(value, type)
  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)


117
118
119
# File 'lib/graphql/schema/argument.rb', line 117

def replace_null_with_default?
  @replace_null_with_default
end

#statically_coercible?Boolean

Returns:

  • (Boolean)


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

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



187
188
189
190
191
192
193
194
195
196
197
# File 'lib/graphql/schema/argument.rb', line 187

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



177
178
179
180
181
182
183
184
185
# File 'lib/graphql/schema/argument.rb', line 177

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.



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
379
# File 'lib/graphql/schema/argument.rb', line 354

def validate_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)


146
147
148
# File 'lib/graphql/schema/argument.rb', line 146

def visible?(context)
  true
end