Class: GraphQL::Schema::Resolver

Inherits:
Object
  • Object
show all
Extended by:
Member::BaseDSLMethods, Member::HasArguments, Member::HasAuthorization, Member::HasDeprecationReason, Member::HasDirectives, Member::HasPath, Member::HasValidators
Includes:
Member::GraphQLTypeNames, Member::HasDataloader, Member::HasPath
Defined in:
lib/graphql/schema/resolver.rb,
lib/graphql/schema/resolver/has_payload_type.rb

Overview

A class-based container for field configuration and resolution logic. It supports:

  • Arguments, via .argument(...) helper, which will be applied to the field.
  • Return type, via .type(..., null: ...), which will be applied to the field.
  • Description, via .description(...), which will be applied to the field
  • Comment, via .comment(...), which will be applied to the field
  • Resolution, via #resolve(**args) method, which will be called to resolve the field.
  • #object and #context accessors for use during #resolve.

Resolvers can be attached with the resolver: option in a field(...) call.

A resolver's configuration may be overridden with other keywords in the field(...) call.

See Also:

  • for a concrete subclass of `Resolver`.
  • `Resolver` is a replacement for `GraphQL::Function`

Direct Known Subclasses

Mutation, Subscription

Defined Under Namespace

Modules: HasPayloadType

Constant Summary

Constants included from Member::HasArguments

Member::HasArguments::NO_ARGUMENTS

Constants included from EmptyObjects

EmptyObjects::EMPTY_ARRAY, EmptyObjects::EMPTY_HASH

Constants included from Member::GraphQLTypeNames

Member::GraphQLTypeNames::Boolean, Member::GraphQLTypeNames::ID, Member::GraphQLTypeNames::Int

Instance Attribute Summary collapse

Attributes included from Member::BaseDSLMethods

#default_graphql_name, #graphql_name

Attributes included from Member::HasDeprecationReason

#deprecation_reason

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Member::BaseDSLMethods

comment, default_relay?, description, introspection, introspection?, mutation, visible?

Methods included from Member::HasArguments

add_argument, all_argument_definitions, any_arguments?, argument, argument_class, arguments_statically_coercible?, coerce_arguments, own_arguments, remove_argument, validate_directive_argument

Methods included from Member::HasValidators

validates, validators

Methods included from Member::HasPath

path

Methods included from Member::HasDirectives

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

Methods included from Member::HasDataloader

#dataload, #dataload_all, #dataload_all_associations, #dataload_all_records, #dataload_association, #dataload_record, #dataloader

Constructor Details

#initialize(object:, context:, field:) ⇒ Resolver

Returns a new instance of Resolver.

Parameters:



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/graphql/schema/resolver.rb', line 37

def initialize(object:, context:, field:)
  @object = object
  @context = context
  @field = field
  # Since this hash is constantly rebuilt, cache it for this call
  @arguments_by_keyword = {}
  context.types.arguments(self.class).each do |arg|
    @arguments_by_keyword[arg.keyword] = arg
  end
  @prepared_arguments = nil
end

Instance Attribute Details

#contextGraphQL::Query::Context (readonly)



55
56
57
# File 'lib/graphql/schema/resolver.rb', line 55

def context
  @context
end

#exec_indexObject

Returns the value of attribute exec_index.



49
50
51
# File 'lib/graphql/schema/resolver.rb', line 49

def exec_index
  @exec_index
end

#exec_resultObject

Returns the value of attribute exec_result.



49
50
51
# File 'lib/graphql/schema/resolver.rb', line 49

def exec_result
  @exec_result
end

#fieldGraphQL::Schema::Field (readonly)



58
59
60
# File 'lib/graphql/schema/resolver.rb', line 58

def field
  @field
end

#field_resolve_stepObject

Returns the value of attribute field_resolve_step.



49
50
51
# File 'lib/graphql/schema/resolver.rb', line 49

def field_resolve_step
  @field_resolve_step
end

#objectObject

Returns The application object this field is being resolved on.

Returns:

  • (Object)

    The application object this field is being resolved on



52
53
54
# File 'lib/graphql/schema/resolver.rb', line 52

def object
  @object
end

#prepared_arguments=(value) ⇒ Object (writeonly)

Sets the attribute prepared_arguments

Parameters:

  • value

    the value to set the attribute prepared_arguments to.



60
61
62
# File 'lib/graphql/schema/resolver.rb', line 60

def prepared_arguments=(value)
  @prepared_arguments = value
end

#raw_argumentsObject

Returns the value of attribute raw_arguments.



49
50
51
# File 'lib/graphql/schema/resolver.rb', line 49

def raw_arguments
  @raw_arguments
end

Class Method Details

.all_field_argument_definitionsObject



307
308
309
# File 'lib/graphql/schema/resolver.rb', line 307

def all_field_argument_definitions
  all_argument_definitions
end

.any_field_arguments?Boolean

Returns:



299
300
301
# File 'lib/graphql/schema/resolver.rb', line 299

def any_field_arguments?
  any_arguments?
end

.argument(*args, **kwargs, &block) ⇒ Object

Add an argument to this field's signature, but also add some preparation hook methods which will be used for this argument

See Also:

  • for the signature


448
449
450
451
452
# File 'lib/graphql/schema/resolver.rb', line 448

def argument(*args, **kwargs, &block)
  # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
  # so that we can support `#load_{x}` methods below.
  super(*args, from_resolver: true, **kwargs)
end

.authorizes?(context) ⇒ Boolean

Returns:



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

def self.authorizes?(context)
  self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver
end

.broadcastable(new_broadcastable) ⇒ Object



385
386
387
# File 'lib/graphql/schema/resolver.rb', line 385

def broadcastable(new_broadcastable)
  @broadcastable = new_broadcastable
end

.broadcastable?Boolean?

Returns:



390
391
392
393
394
395
396
# File 'lib/graphql/schema/resolver.rb', line 390

def broadcastable?
  if defined?(@broadcastable)
    @broadcastable
  else
    (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil)
  end
end

.complexity(new_complexity = nil) ⇒ Integer, Proc

Specifies the complexity of the field. Defaults to 1

Returns:

  • (Integer, Proc)


378
379
380
381
382
383
# File 'lib/graphql/schema/resolver.rb', line 378

def complexity(new_complexity = nil)
  if new_complexity
    @complexity = new_complexity
  end
  @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
end

.default_page_size(new_default_page_size = NOT_CONFIGURED) ⇒ Integer?

Get or set the default_page_size: which will be configured for fields using this resolver (nil means "unlimited default page size".)

Parameters:

  • default_page_size (Integer, nil)

    Set a new value

Returns:

  • (Integer, nil)

    The default_page_size assigned to fields that use this resolver



423
424
425
426
427
428
429
430
431
432
433
# File 'lib/graphql/schema/resolver.rb', line 423

def default_page_size(new_default_page_size = NOT_CONFIGURED)
  if new_default_page_size != NOT_CONFIGURED
    @default_page_size = new_default_page_size
  elsif defined?(@default_page_size)
    @default_page_size
  elsif superclass.respond_to?(:default_page_size)
    superclass.default_page_size
  else
    nil
  end
end

.extension(extension, **options) ⇒ Object

Registers new extension

Parameters:

  • extension (Class)

    Extension class

  • options (Hash)

    Optional extension options



457
458
459
460
# File 'lib/graphql/schema/resolver.rb', line 457

def extension(extension, **options)
  @own_extensions ||= []
  @own_extensions << {extension => options}
end

.extensionsObject

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.



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/graphql/schema/resolver.rb', line 463

def extensions
  own_exts = @own_extensions
  # Jump through some hoops to avoid creating arrays when we don't actually need them
  if superclass.respond_to?(:extensions)
    s_exts = superclass.extensions
    if own_exts
      if !s_exts.empty?
        own_exts + s_exts
      else
        own_exts
      end
    else
      s_exts
    end
  else
    own_exts || EMPTY_ARRAY
  end
end

.extras(new_extras = nil) ⇒ Object

Additional info injected into #resolve

See Also:

  • {GraphQL{GraphQL::Schema{GraphQL::Schema::Field{GraphQL::Schema::Field#extras}


322
323
324
325
326
327
328
# File 'lib/graphql/schema/resolver.rb', line 322

def extras(new_extras = nil)
  if new_extras
    @own_extras = new_extras
  end
  own_extras = @own_extras || []
  own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
end

.field_arguments(context = GraphQL::Query::NullContext.instance) ⇒ Object



295
296
297
# File 'lib/graphql/schema/resolver.rb', line 295

def field_arguments(context = GraphQL::Query::NullContext.instance)
  arguments(context)
end

.get_field_argument(name, context = GraphQL::Query::NullContext.instance) ⇒ Object



303
304
305
# File 'lib/graphql/schema/resolver.rb', line 303

def get_field_argument(name, context = GraphQL::Query::NullContext.instance)
  get_argument(name, context)
end

.has_default_page_size?Boolean

Returns true if this resolver or a superclass has an assigned default_page_size.

Returns:

  • (Boolean)

    true if this resolver or a superclass has an assigned default_page_size



436
437
438
# File 'lib/graphql/schema/resolver.rb', line 436

def has_default_page_size?
  (!!defined?(@default_page_size)) || (superclass.respond_to?(:has_default_page_size?) && superclass.has_default_page_size?)
end

.has_max_page_size?Boolean

Returns true if this resolver or a superclass has an assigned max_page_size.

Returns:

  • (Boolean)

    true if this resolver or a superclass has an assigned max_page_size



415
416
417
# File 'lib/graphql/schema/resolver.rb', line 415

def has_max_page_size?
  (!!defined?(@max_page_size)) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
end

.max_page_size(new_max_page_size = NOT_CONFIGURED) ⇒ Integer?

Get or set the max_page_size: which will be configured for fields using this resolver (nil means "unlimited max page size".)

Parameters:

  • max_page_size (Integer, nil)

    Set a new value

Returns:

  • (Integer, nil)

    The max_page_size assigned to fields that use this resolver



402
403
404
405
406
407
408
409
410
411
412
# File 'lib/graphql/schema/resolver.rb', line 402

def max_page_size(new_max_page_size = NOT_CONFIGURED)
  if new_max_page_size != NOT_CONFIGURED
    @max_page_size = new_max_page_size
  elsif defined?(@max_page_size)
    @max_page_size
  elsif superclass.respond_to?(:max_page_size)
    superclass.max_page_size
  else
    nil
  end
end

.null(allow_null = nil) ⇒ Object

If true (default), then the return type for this resolver will be nullable. If false, then the return type is non-null.

Parameters:

  • allow_null (Boolean) (defaults to: nil)

    Whether or not the response can be null

See Also:

  • which sets the return type of this field and accepts a `null:` option


335
336
337
338
339
340
341
# File 'lib/graphql/schema/resolver.rb', line 335

def null(allow_null = nil)
  if !allow_null.nil?
    @null = allow_null
  end

  @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null
end

.resolve_method(new_method = nil) ⇒ Symbol

Default :resolve set below.

Returns:

  • (Symbol)

    The method to call on instances of this object to resolve the field



313
314
315
316
317
318
# File 'lib/graphql/schema/resolver.rb', line 313

def resolve_method(new_method = nil)
  if new_method
    @resolve_method = new_method
  end
  @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve)
end

.resolver_method(new_method_name = nil) ⇒ Object



343
344
345
346
347
348
349
# File 'lib/graphql/schema/resolver.rb', line 343

def resolver_method(new_method_name = nil)
  if new_method_name
    @resolver_method = new_method_name
  else
    @resolver_method || :resolve_with_support
  end
end

.type(new_type = nil, null: nil) ⇒ Class

Call this method to get the return type of the field, or use it as a configuration method to assign a return type instead of generating one. TODO unify with #null

Parameters:

  • new_type (Class, Array<Class>, nil) (defaults to: nil)

    If a type definition class is provided, it will be used as the return type of the field

  • null (true, false) (defaults to: nil)

    Whether or not the field may return nil

Returns:

  • (Class)

    The type which this field returns.



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/graphql/schema/resolver.rb', line 358

def type(new_type = nil, null: nil)
  if new_type
    if null.nil?
      raise ArgumentError, "required argument `null:` is missing"
    end
    @type_expr = new_type
    @null = null
  else
    if type_expr
      GraphQL::Schema::Member::BuildType.parse_type(type_expr, null: self.null)
    elsif superclass.respond_to?(:type)
      superclass.type
    else
      nil
    end
  end
end

.type_exprObject

A non-normalized type configuration, without null applied



441
442
443
# File 'lib/graphql/schema/resolver.rb', line 441

def type_expr
  @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil)
end

Instance Method Details

#argumentsObject



128
129
130
# File 'lib/graphql/schema/resolver.rb', line 128

def arguments
  @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
end

#authorized?(**inputs) ⇒ Boolean, early_return_data

Called after arguments are loaded, but before resolving.

Override it to check everything before calling the mutation.

Parameters:

  • inputs (Hash)

    The input arguments

Returns:

  • (Boolean, early_return_data)

    If false, execution will stop (and early_return_data will be returned instead, if present.)

Raises:



222
223
224
225
226
# File 'lib/graphql/schema/resolver.rb', line 222

def authorized?(**inputs)
  arg_owner = @field # || self.class
  args = context.types.arguments(arg_owner)
  authorize_arguments(args, inputs)
end

#callObject



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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/graphql/schema/resolver.rb', line 62

def call
  q = context.query
  trace_objs = [object]
  q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
  is_ready = ready?(**@prepared_arguments)
  runner = @field_resolve_step.runner
  if runner.resolves_lazies && runner.schema.lazy?(is_ready)
    is_ready, new_return_value = runner.schema.sync_lazy(is_ready)
  end

  if is_ready.is_a?(Array)
    is_ready, new_return_value = is_ready
    if is_ready != false
      raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{new_return_value.inspect}]"
    else
      new_return_value
    end
  end

  if is_ready
    begin
      is_authed, new_return_value = authorized?(**@prepared_arguments)
    rescue GraphQL::UnauthorizedError  => err
      new_return_value = q.schema.unauthorized_object(err)
      is_authed = true # the error was handled
    end
  end

  if runner.resolves_lazies && runner.schema.lazy?(is_authed)
    is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
  end

  result = if is_authed
    Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
    call_resolve(@prepared_arguments)
  elsif new_return_value.nil?
    err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
    context.schema.unauthorized_field(err)
  else
    new_return_value
  end
  q = context.query
  q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
  exec_result[exec_index] = result
rescue GraphQL::UnauthorizedError => auth_err
  exec_result[exec_index] = begin
    context.schema.unauthorized_object(auth_err)
  rescue GraphQL::ExecutionError => exec_err
    exec_err
  end
rescue GraphQL::RuntimeError => err
  exec_result[exec_index] = err
rescue StandardError => stderr
  exec_result[exec_index] = begin
    context.query.handle_or_reraise(stderr)
  rescue GraphQL::ExecutionError => ex_err
    ex_err
  end
ensure
  field_pending_steps = field_resolve_step.pending_steps
  field_pending_steps.delete(self)
  if field_pending_steps.size == 0 && field_resolve_step.field_results
    field_resolve_step.runner.add_step(field_resolve_step)
  end
end

#call_resolve(args_hash) ⇒ Object



187
188
189
190
191
192
193
# File 'lib/graphql/schema/resolver.rb', line 187

def call_resolve(args_hash)
  if !args_hash.empty?
    public_send(self.class.resolve_method, **args_hash)
  else
    public_send(self.class.resolve_method)
  end
end

#ready?(**args) ⇒ Boolean, early_return_data

Called before arguments are prepared. Implement this hook to make checks before doing any work.

If it returns a lazy object (like a promise), it will be synced by GraphQL (but the resulting value won't be used).

Parameters:

  • args (Hash)

    The input arguments, if there are any

Returns:

  • (Boolean, early_return_data)

    If false, execution will stop (and early_return_data will be returned instead, if present.)

Raises:



211
212
213
# File 'lib/graphql/schema/resolver.rb', line 211

def ready?(**args)
  true
end

#resolve(**args) ⇒ Object

Do the work. Everything happens here.

Returns:

  • (Object)

    An object corresponding to the return type

Raises:



197
198
199
# File 'lib/graphql/schema/resolver.rb', line 197

def resolve(**args)
  raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic"
end

#resolve_with_support(**args) ⇒ 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.

This method is actually called by the runtime, it does some preparation and then eventually calls the user-defined #resolve method.



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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/graphql/schema/resolver.rb', line 136

def resolve_with_support(**args)
  # First call the ready? hook which may raise
  raw_ready_val = if !args.empty?
    ready?(**args)
  else
    ready?
  end
  context.query.after_lazy(raw_ready_val) do |ready_val|
    if ready_val.is_a?(Array)
      is_ready, ready_early_return = ready_val
      if is_ready != false
        raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]"
      else
        ready_early_return
      end
    elsif ready_val
      # Then call each prepare hook, which may return a different value
      # for that argument, or may return a lazy object
      load_arguments_val = load_arguments(args)
      context.query.after_lazy(load_arguments_val) do |loaded_args|
        @prepared_arguments = loaded_args
        Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
        # Then call `authorized?`, which may raise or may return a lazy object
        raw_authorized_val = if !loaded_args.empty?
          authorized?(**loaded_args)
        else
          authorized?
        end
        context.query.after_lazy(raw_authorized_val) do |authorized_val|
          # If the `authorized?` returned two values, `false, early_return`,
          # then use the early return value instead of continuing
          if authorized_val.is_a?(Array)
            authorized_result, early_return = authorized_val
            if authorized_result == false
              early_return
            else
              raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]"
            end
          elsif authorized_val
            # Finally, all the hooks have passed, so resolve it
            call_resolve(loaded_args)
          else
            raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
          end
        end
      end
    end
  end
end

#unauthorized_object(err) ⇒ Object

Called when an object loaded by loads: fails the .authorized? check for its resolved GraphQL object type.

By default, the error is re-raised and passed along to GraphQL::Schema::Resolver.{Schema{Schema.unauthorized_object}.

Any value returned here will be used instead of of the loaded object.

Parameters:



238
239
240
# File 'lib/graphql/schema/resolver.rb', line 238

def unauthorized_object(err)
  raise err
end