Class: GraphQL::Schema::Argument

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

Methods included from Member::CachedGraphQLDefinition

#graphql_definition, #initialize_copy, #type_class

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, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &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) (defaults to: true)

    if true, this argument is non-null; if false, this argument is nullable

  • 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

  • method_access (Boolean) (defaults to: true)

    If false, don't build method access on legacy Query::Arguments instances.

  • 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



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

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, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &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
  @default_value = default_value
  @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
  @method_access = method_access
  self.deprecation_reason = deprecation_reason

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

  self.validates(validates)

  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



90
91
92
# File 'lib/graphql/schema/argument.rb', line 90

def default_value
  @default_value
end

#description(text = nil) ⇒ String

Returns Documentation for this argument.

Returns:

  • (String)

    Documentation for this argument



100
101
102
103
104
105
106
# File 'lib/graphql/schema/argument.rb', line 100

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



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

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



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

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



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

def name
  @name
end

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

Returns The field or input object this argument belongs to.

Returns:



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

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



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

def prepare
  @prepare
end

Instance Method Details

#accessible?(context) ⇒ Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/graphql/schema/argument.rb', line 126

def accessible?(context)
  true
end

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

Returns:

  • (Boolean)


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

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)


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/graphql/schema/argument.rb', line 134

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?
    as_type.arguments(ctx).each do |_name, input_obj_arg|
      input_obj_arg = input_obj_arg.type_class
      # TODO: this skips input objects whose values were alread replaced with application objects.
      # See: https://github.com/rmosolgo/graphql-ruby/issues/2633
      if value.is_a?(InputObject) && value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
        return false
      end
    end
  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
# 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

  loaded_value = nil
  coerced_value = context.schema.error_handler.with_error_handling(context) do
    type.coerce_input(value, context)
  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|
    if loads && !from_resolver?
      loaded_value = context.query.with_error_handling do
        load_and_authorize_value(owner, coerced_value, context)
      end
    end

    maybe_loaded_value = loaded_value || resolved_coerced_value
    context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
      owner.validate_directive_argument(self, resolved_loaded_value)
      prepared_value = context.schema.error_handler.with_error_handling(context) do
        prepare_value(parent_object, resolved_loaded_value, context: context)
      end

      # TODO code smell to access such a deeply-nested constant in a distant module
      argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
        value: prepared_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



93
94
95
# File 'lib/graphql/schema/argument.rb', line 93

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



109
110
111
112
113
114
115
# File 'lib/graphql/schema/argument.rb', line 109

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

#deprecation_reason=(new_reason) ⇒ Object



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

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



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

def from_resolver?
  @from_resolver
end

#inspectObject



85
86
87
# File 'lib/graphql/schema/argument.rb', line 85

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.



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 213

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

#statically_coercible?Boolean

Returns:

  • (Boolean)


204
205
206
207
208
# File 'lib/graphql/schema/argument.rb', line 204

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

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

#to_graphqlObject



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/graphql/schema/argument.rb', line 164

def to_graphql
  argument = GraphQL::Argument.new
  argument.name = @name
  argument.type = -> { type }
  argument.description = @description
  argument.[:type_class] = self
  argument.as = @as
  argument.ast_node = ast_node
  argument.method_access = @method_access
  if NO_DEFAULT != @default_value
    argument.default_value = @default_value
  end
  if self.deprecation_reason
    argument.deprecation_reason = self.deprecation_reason
  end
  argument
end

#typeObject



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/graphql/schema/argument.rb', line 192

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



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

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)


122
123
124
# File 'lib/graphql/schema/argument.rb', line 122

def visible?(context)
  true
end