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, 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)
  • 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
98
# 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, 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)
  @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
    if definition_block.arity == 1
      instance_exec(self, &definition_block)
    else
      instance_eval(&definition_block)
    end
  end
end

Instance Attribute Details

#comment(text = nil) ⇒ String

Returns Comment for this argument.

Returns:

  • (String)

    Comment for this argument



136
137
138
139
140
141
142
# File 'lib/graphql/schema/argument.rb', line 136

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



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

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)


162
163
164
# File 'lib/graphql/schema/argument.rb', line 162

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)


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/graphql/schema/argument.rb', line 166

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.



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
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/graphql/schema/argument.rb', line 252

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



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

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



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

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



145
146
147
148
149
150
151
# File 'lib/graphql/schema/argument.rb', line 145

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

#deprecation_reason=(new_reason) ⇒ Object



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

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



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

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



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
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/graphql/schema/argument.rb', line 313

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.



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/graphql/schema/argument.rb', line 220

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)


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

def replace_null_with_default?
  @replace_null_with_default
end

#statically_coercible?Boolean

Returns:

  • (Boolean)


211
212
213
214
215
# File 'lib/graphql/schema/argument.rb', line 211

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



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

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



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

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.



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/graphql/schema/argument.rb', line 366

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)


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

def visible?(context)
  true
end