Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions lib/graphql/schema/member/has_arguments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,8 @@ def authorize_application_object(argument, id, context, loaded_application_objec
if application_object.nil?
nil
else
maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
arg_loads_type = argument.loads
maybe_lazy_resolve_type = context.schema.resolve_type(arg_loads_type, application_object, context)
context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
application_object_type, application_object = resolve_type_result
Expand All @@ -368,10 +369,17 @@ def authorize_application_object(argument, id, context, loaded_application_objec
# application_object is already assigned
end

if !(
context.types.possible_types(argument.loads).include?(application_object_type) ||
context.types.loadable?(argument.loads, context)
)
passes_possible_types_check = if context.types.loadable?(arg_loads_type, context)
if arg_loads_type.kind.union?
# This union is used in `loads:` but not otherwise visible to this query
context.types.loadable_possible_types(arg_loads_type, context).include?(application_object_type)
else
true
end
else
context.types.possible_types(arg_loads_type).include?(application_object_type)
end
if !passes_possible_types_check
err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
application_object = load_application_object_failed(err)
end
Expand Down
1 change: 1 addition & 0 deletions lib/graphql/schema/visibility/migration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def loaded_types
:all_types_h,
:fields,
:loadable?,
:loadable_possible_types,
:type,
:arguments,
:argument,
Expand Down
6 changes: 6 additions & 0 deletions lib/graphql/schema/visibility/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def initialize(name: nil, context:, schema:)
@cached_arguments = Hash.new do |h, owner|
h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
end.compare_by_identity

@loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
end

def field_on_visible_interface?(field, owner)
Expand Down Expand Up @@ -249,6 +251,10 @@ def loadable?(t, _ctx)
!@all_types[t.graphql_name] && @cached_visible[t]
end

def loadable_possible_types(t, _ctx)
@loadable_possible_types[t]
end

def loaded_types
@all_types.values
end
Expand Down
15 changes: 14 additions & 1 deletion lib/graphql/schema/warden.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
def arguments(owner, ctx); owner.arguments(ctx); end
def loadable?(type, ctx); type.visible?(ctx); end
def loadable_possible_types(type, ctx); type.possible_types(ctx); end
def visibility_profile
@visibility_profile ||= Warden::VisibilityProfile.new(self)
end
Expand Down Expand Up @@ -106,6 +107,7 @@ def fields(type_defn); type_defn.all_field_definitions; end # rubocop:disable De
def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
def reachable_type?(type_name); true; end
def loadable?(type, _ctx); true; end
def loadable_possible_types(union_type, _ctx); union_type.possible_types; end
def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
def interfaces(obj_type); obj_type.interfaces; end
Expand Down Expand Up @@ -180,6 +182,10 @@ def loadable?(t, ctx) # TODO remove ctx here?
@warden.loadable?(t, ctx)
end

def loadable_possible_types(t, ctx)
@warden.loadable_possible_types(t, ctx)
end

def reachable_type?(type_name)
[email protected]_type?(type_name)
end
Expand All @@ -204,7 +210,7 @@ def initialize(context:, schema:)
@visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
@visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
@visible_and_reachable_type = @unions = @unfiltered_interfaces =
@reachable_type_set = @visibility_profile =
@reachable_type_set = @visibility_profile = @loadable_possible_types =
nil
@skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
end
Expand All @@ -229,6 +235,13 @@ def loadable?(type, _ctx)
!reachable_type_set.include?(type) && visible_type?(type)
end

def loadable_possible_types(union_type, _ctx)
@loadable_possible_types ||= read_through do |t|
t.possible_types # unfiltered
end
@loadable_possible_types[union_type]
end

# @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
def get_type(type_name)
@visible_types ||= read_through do |name|
Expand Down
54 changes: 54 additions & 0 deletions spec/graphql/schema/union_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -406,4 +406,58 @@ def unboxed_union
end
end
end

describe "use with loads:" do
class UnionLoadsSchema < GraphQL::Schema
class Image < GraphQL::Schema::Object
field :title, String
end

class Video < GraphQL::Schema::Object
field :title, String
end

class Post < GraphQL::Schema::Object
field :title, String
end

class MediaItem < GraphQL::Schema::Union
possible_types Image, Video
end

class Query < GraphQL::Schema::Object
field :media_item_type, String do
argument :id, ID, loads: MediaItem, as: :media_item
end

def media_item_type(media_item:)
media_item[:type]
end
end

query(Query)

def self.object_from_id(id, ctx)
type, title = id.split("/")
{ type: type, title: title }
end

def self.resolve_type(abs_type, obj, ctx)
UnionLoadsSchema.const_get(obj[:type])
end
end

it "restricts to members of the union" do
query_str = "query($mediaId: ID!) { mediaItemType(id: $mediaId) }"
res = UnionLoadsSchema.execute(query_str, variables: { mediaId: "Image/Family Photo" })
assert_equal "Image", res["data"]["mediaItemType"]

res = UnionLoadsSchema.execute(query_str, variables: { mediaId: "Video/Christmas Pageant" })
assert_equal "Video", res["data"]["mediaItemType"]

res = UnionLoadsSchema.execute(query_str, variables: { mediaId: "Post/Year in Review" })
assert_nil res["data"]["mediaItemType"]
assert_equal ["No object found for `id: \"Post/Year in Review\"`"], res["errors"].map { |e| e["message"] }
end
end
end
Loading