1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 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 127 128
|
# frozen_string_literal: true
require "graphql/query/null_context"
module GraphQL
class Schema
class Object < GraphQL::Schema::Member
extend GraphQL::Schema::Member::HasFields
extend GraphQL::Schema::Member::HasInterfaces
# @return [Object] the application object this type is wrapping
attr_reader :object
# @return [GraphQL::Query::Context] the context instance for this query
attr_reader :context
# @return [GraphQL::Dataloader]
def dataloader
context.dataloader
end
# Call this in a field method to return a value that should be returned to the client
# without any further handling by GraphQL.
def raw_value(obj)
GraphQL::Execution::Interpreter::RawValue.new(obj)
end
class << self
# This is protected so that we can be sure callers use the public method, {.authorized_new}
# @see authorized_new to make instances
protected :new
def wrap_scoped(object, context)
scoped_new(object, context)
end
# This is called by the runtime to return an object to call methods on.
def wrap(object, context)
authorized_new(object, context)
end
# Make a new instance of this type _if_ the auth check passes,
# otherwise, raise an error.
#
# Probably only the framework should call this method.
#
# This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?`
# hook returns some lazy value (like a Promise).
#
# The reason that the auth check is in this wrapper method instead of {.new} is because
# of how it might return a Promise. It would be weird if `.new` returned a promise;
# It would be a headache to try to maintain Promise-y state inside a {Schema::Object}
# instance. So, hopefully this wrapper method will do the job.
#
# @param object [Object] The thing wrapped by this object
# @param context [GraphQL::Query::Context]
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
def authorized_new(object, context)
maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
begin
authorized?(object, context)
rescue GraphQL::UnauthorizedError => err
context.schema.unauthorized_object(err)
rescue StandardError => err
context.query.handle_or_reraise(err)
end
end
auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
GraphQL::Execution::Lazy.new do
context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
context.schema.sync_lazy(maybe_lazy_auth_val)
end
end
else
maybe_lazy_auth_val
end
context.query.after_lazy(auth_val) do |is_authorized|
if is_authorized
self.new(object, context)
else
# It failed the authorization check, so go to the schema's authorized object hook
err = GraphQL::UnauthorizedError.new(object: object, type: self, context: context)
# If a new value was returned, wrap that instead of the original value
begin
new_obj = context.schema.unauthorized_object(err)
if new_obj
self.new(new_obj, context)
else
nil
end
end
end
end
end
def scoped_new(object, context)
self.new(object, context)
end
end
def initialize(object, context)
@object = object
@context = context
end
class << self
# Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
# It should help with debugging and bug tracker integrations.
def const_missing(name)
if name == :InvalidNullError
custom_err_class = GraphQL::InvalidNullError.subclass_for(self)
const_set(:InvalidNullError, custom_err_class)
custom_err_class
else
super
end
end
def kind
GraphQL::TypeKinds::OBJECT
end
end
end
end
end
|