Class: GraphQL::Schema::Argument

Inherits:
Object
  • Object
show all
Includes:
FindInheritedValue::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 collapse

NO_DEFAULT =
:__no_default__

Constants included from FindInheritedValue::EmptyObjects

FindInheritedValue::EmptyObjects::EMPTY_ARRAY, FindInheritedValue::EmptyObjects::EMPTY_HASH

Constants included from Member::HasDirectives

Member::HasDirectives::NO_DIRECTIVES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Member::HasValidators

#validates, #validators

Methods included from Member::HasDirectives

#directive, #directives, #remove_directive

Methods included from Member::HasAstNode

#ast_node

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: NO_DEFAULT, 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: NO_DEFAULT)
  • 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


49
50
51
52
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
# File 'lib/graphql/schema/argument.rb', line 49

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: NO_DEFAULT, 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

#default_valueObject (readonly)

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

Returns:

  • (Object)

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


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

def default_value
  @default_value
end

#description(text = nil) ⇒ String

Returns Documentation for this argument.

Returns:

  • (String)

    Documentation for this argument


114
115
116
117
118
119
120
# File 'lib/graphql/schema/argument.rb', line 114

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


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

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


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

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


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

def name
  @name
end

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

Returns The field or input object this argument belongs to.

Returns:


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

def owner
  @owner
end

#prepareSymbol (readonly)

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

Returns:

  • (Symbol)

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


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

def prepare
  @prepare
end

Instance Method Details

#accessible?(context) ⇒ Boolean

Returns:

  • (Boolean)

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

def accessible?(context)
  true
end

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

Returns:

  • (Boolean)

144
145
146
# File 'lib/graphql/schema/argument.rb', line 144

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)

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/graphql/schema/argument.rb', line 148

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.


229
230
231
232
233
234
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/graphql/schema/argument.rb', line 229

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.schema.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.schema.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,
        definition: self,
        default_used: default_used,
      )
    end
  end
end

#default_value?Boolean

Returns True if this argument has a default value.

Returns:

  • (Boolean)

    True if this argument has a default value


103
104
105
# File 'lib/graphql/schema/argument.rb', line 103

def default_value?
  @default_value != NO_DEFAULT
end

#deprecation_reason(text = nil) ⇒ String

Returns Deprecation reason for this argument.

Returns:

  • (String)

    Deprecation reason for this argument


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

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

#deprecation_reason=(new_reason) ⇒ Object


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

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


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

def from_resolver?
  @from_resolver
end

#inspectObject


95
96
97
# File 'lib/graphql/schema/argument.rb', line 95

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


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

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.schema.after_lazy(custom_loaded_value) do |custom_value|
      if loads
        if type.list?
          loaded_values = custom_value.each_with_index.map { |custom_val, idx|
            id = coerced_value[idx]
            load_method_owner.authorize_application_object(self, id, context, custom_val)
          }
          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 = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
      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_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.


202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/graphql/schema/argument.rb', line 202

def prepare_value(obj, value, context: nil)
  if value.is_a?(GraphQL::Schema::InputObject)
    value = value.prepare
  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
    else
      obj.public_send(@prepare, value)
    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)

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

def replace_null_with_default?
  @replace_null_with_default
end

#statically_coercible?Boolean

Returns:

  • (Boolean)

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

def statically_coercible?
  return @statically_coercible if defined?(@statically_coercible)

  @statically_coercible = !@prepare.is_a?(String) && !@prepare.is_a?(Symbol)
end

#typeObject


181
182
183
184
185
186
187
188
189
190
191
# File 'lib/graphql/schema/argument.rb', line 181

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


171
172
173
174
175
176
177
178
179
# File 'lib/graphql/schema/argument.rb', line 171

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.


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

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)

136
137
138
# File 'lib/graphql/schema/argument.rb', line 136

def visible?(context)
  true
end