Skip to content

Commit

Permalink
feat(InterfaceType) add type-level resolve_type support
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Jul 12, 2017
1 parent 8469d8f commit 75497f2
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 37 deletions.
16 changes: 13 additions & 3 deletions lib/graphql/backwards_compatibility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ module BackwardsCompatibility
# check its arity, and if needed, apply a wrapper so that
# it can be called with `to` arguments.
# If a wrapper is applied, warn the application with `name`.
def wrap_arity(callable, from:, to:, name:)
#
# If `last`, then use the last arguments to call the function.
def wrap_arity(callable, from:, to:, name:, last: false)
arity = get_arity(callable)
case arity
when to
Expand All @@ -17,7 +19,8 @@ def wrap_arity(callable, from:, to:, name:)
when from
# It has the old arity, so wrap it with an arity converter
warn("#{name} with #{from} arguments is deprecated, it now accepts #{to} arguments")
ArityWrapper.new(callable, from)
wrapper = last ? LastArgumentsWrapper : FirstArgumentsWrapper
wrapper.new(callable, from)
else
raise "Can't wrap #{callable} (arity: #{arity}) to have arity #{to}"
end
Expand All @@ -32,7 +35,7 @@ def get_arity(callable)
end
end

class ArityWrapper
class FirstArgumentsWrapper
def initialize(callable, old_arity)
@callable = callable
@old_arity = old_arity
Expand All @@ -43,5 +46,12 @@ def call(*args)
@callable.call(*backwards_compat_args)
end
end

class LastArgumentsWrapper < FirstArgumentsWrapper
def call(*args)
backwards_compat_args = args.last(@old_arity)
@callable.call(*backwards_compat_args)
end
end
end
end
13 changes: 9 additions & 4 deletions lib/graphql/interface_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ module GraphQL
# end
#
class InterfaceType < GraphQL::BaseType
accepts_definitions :fields, field: GraphQL::Define::AssignObjectField
accepts_definitions :fields, :resolve_type, field: GraphQL::Define::AssignObjectField

attr_accessor :fields
ensure_defined :fields
attr_accessor :fields, :resolve_type_proc
ensure_defined :fields, :resolve_type_proc, :resolve_type

def initialize
super
@fields = {}
@resolve_type_proc = nil
end

def initialize_copy(other)
Expand All @@ -43,7 +44,11 @@ def kind
end

def resolve_type(value, ctx)
ctx.query.resolve_type(value)
ctx.query.resolve_type(self, value)
end

def resolve_type=(resolve_type_callable)
@resolve_type_proc = resolve_type_callable
end

# @return [GraphQL::Field] The defined field for `field_name`
Expand Down
18 changes: 15 additions & 3 deletions lib/graphql/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ def initialize(schema, query_string = nil, query: nil, document: nil, context: n
@query_string = query_string || query
@document = document

@resolved_types_cache = Hash.new { |h, k| h[k] = @schema.resolve_type(k, @context) }
# A two-layer cache of type resolution:
# { abstract_type => { value => resolved_type } }
@resolved_types_cache = Hash.new do |h1, k1|
h1[k1] = Hash.new do |h2, k2|
h2[k2] = @schema.resolve_type(k1, k2, @context)
end
end

@arguments_cache = ArgumentsCache.build(self)

Expand Down Expand Up @@ -184,11 +190,17 @@ def warden

def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation

# @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType]
# @param value [Object] Any runtime value
# @return [GraphQL::ObjectType, nil] The runtime type of `value` from {Schema#resolve_type}
# @see {#possible_types} to apply filtering from `only` / `except`
def resolve_type(value)
@resolved_types_cache[value]
def resolve_type(abstract_type, value = :__undefined__)
if value.is_a?(Symbol) && value == :__undefined__
# Old method signature
value = abstract_type
abstract_type = nil
end
@resolved_types_cache[abstract_type][value]
end

def mutation?
Expand Down
25 changes: 20 additions & 5 deletions lib/graphql/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -337,15 +337,29 @@ def execution_strategy_for_operation(operation)
# Determine the GraphQL type for a given object.
# This is required for unions and interfaces (including Relay's `Node` interface)
# @see [GraphQL::Schema::Warden] Restricted access to members of a schema
# @param type [GraphQL::UnionType, GraphQL:InterfaceType] the abstract type which is being resolved
# @param object [Any] An application object which GraphQL is currently resolving on
# @param ctx [GraphQL::Query::Context] The context for the current query
# @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL
def resolve_type(object, ctx)
if @resolve_type_proc.nil?
raise(NotImplementedError, "Can't determine GraphQL type for: #{object.inspect}, define `resolve_type (obj, ctx) -> { ... }` inside `Schema.define`.")
def resolve_type(type, object, ctx = :__undefined__)
if ctx == :__undefined__
# Old method signature
ctx = object
object = type
type = nil
end

# Prefer a type-local function; fall back to the schema-level function
type_proc = type && type.resolve_type_proc
type_result = if type_proc
type_proc.call(object, ctx)
else
if @resolve_type_proc.nil?
raise(NotImplementedError, "Can't determine GraphQL type for: #{object.inspect}, define `resolve_type (obj, ctx) -> { ... }` inside `Schema.define`.")
end
@resolve_type_proc.call(type, object, ctx)
end

type_result = @resolve_type_proc.call(object, ctx)
if type_result.nil?
nil
elsif !type_result.is_a?(GraphQL::BaseType)
Expand All @@ -357,7 +371,8 @@ def resolve_type(object, ctx)
end

def resolve_type=(new_resolve_type_proc)
@resolve_type_proc = new_resolve_type_proc
callable = GraphQL::BackwardsCompatibility.wrap_arity(new_resolve_type_proc, from: 2, to: 3, last: true, name: "Schema#resolve_type(type, obj, ctx)")
@resolve_type_proc = callable
end

# Fetch an application object by its unique id
Expand Down
17 changes: 5 additions & 12 deletions lib/graphql/union_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ module GraphQL
#
class UnionType < GraphQL::BaseType
accepts_definitions :possible_types, :resolve_type
ensure_defined :possible_types
ensure_defined :possible_types, :resolve_type, :resolve_type_proc

attr_accessor :resolve_type_proc

def initialize
super
Expand Down Expand Up @@ -66,16 +68,7 @@ def possible_types
end

def resolve_type(value, ctx)
if !@resolve_type_proc.nil?
resolved_type = @resolve_type_proc.call(value, ctx)
if !include?(resolved_type)
raise(NotImplementedError, "Unrecognized possible type kind: #{resolved_type}")
end

return resolved_type
end

ctx.query.resolve_type(value)
ctx.query.resolve_type(self, value)
end

def resolve_type=(new_resolve_type_proc)
Expand All @@ -84,6 +77,6 @@ def resolve_type=(new_resolve_type_proc)

protected

attr_reader :dirty_possible_types, :resolve_type_proc
attr_reader :dirty_possible_types
end
end
2 changes: 1 addition & 1 deletion spec/graphql/analysis/query_complexity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@
GraphQL::Schema.define(
query: query_type,
orphan_types: [double_complexity_type],
resolve_type: :pass
resolve_type: ->(a,b,c) { :pass }
)
}
let(:query_string) {%|
Expand Down
1 change: 1 addition & 0 deletions spec/graphql/introspection/schema_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
{"name"=>"allAnimalAsCow"},
{"name"=>"allDairy"},
{"name"=>"allEdible"},
{"name"=>"allEdibleAsMilk"},
{"name"=>"cheese"},
{"name"=>"cow"},
{"name"=>"dairy"},
Expand Down
2 changes: 1 addition & 1 deletion spec/graphql/query/executor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
end
end

GraphQL::Schema.define(query: DummyQueryType, mutation: Dummy::DairyAppMutationType, resolve_type: :pass, id_from_object: :pass)
GraphQL::Schema.define(query: DummyQueryType, mutation: Dummy::DairyAppMutationType, resolve_type: ->(a,b,c) { :pass }, id_from_object: :pass)
}
let(:variables) { nil }
let(:query_string) { %|
Expand Down
2 changes: 1 addition & 1 deletion spec/graphql/schema/loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
query: query_root,
mutation: mutation_root,
orphan_types: [audio_type, video_type],
resolve_type: :pass,
resolve_type: ->(a,b,c) { :pass },
)
}

Expand Down
2 changes: 1 addition & 1 deletion spec/graphql/schema/printer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
query: query_root,
mutation: mutation_root,
subscription: subscription_root,
resolve_type: :pass,
resolve_type: ->(a,b,c) { :pass },
orphan_types: [media_union_type]
)
}
Expand Down
13 changes: 7 additions & 6 deletions spec/graphql/union_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@
end

it '#resolve_type raises error if resolved type is not in possible_types' do
assert_raises(NotImplementedError) {
test_str = 'Hello world'
union.resolve_type = ->(value, ctx) {
"This is not the types you are looking for"
}
test_str = 'Hello world'
union.resolve_type = ->(value, ctx) {
"This is not the types you are looking for"
}
fake_ctx = OpenStruct.new(query: GraphQL::Query.new(Dummy::Schema, ""))

union.resolve_type(test_str, nil)
assert_raises(RuntimeError) {
union.resolve_type(test_str, fake_ctx)
}
end

Expand Down
13 changes: 13 additions & 0 deletions spec/support/dummy/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ class NoSuchDairyError < StandardError; end
field :selfAsEdible, EdibleInterface, resolve: ->(o, a, c) { o }
end

EdibleAsMilkInterface = EdibleInterface.redefine do
name "EdibleAsMilk"
description "Milk :+1:"
field :fatContent, !types.Float, "Percentage which is fat"
field :origin, !types.String, "Place the edible comes from"
field :selfAsEdible, EdibleInterface, resolve: ->(o, a, c) { o }
resolve_type ->(obj, ctx) { MilkType }
end

AnimalProductInterface = GraphQL::InterfaceType.define do
name "AnimalProduct"
description "Comes from an animal, no joke"
Expand Down Expand Up @@ -331,6 +340,10 @@ def call(obj, args, ctx)
resolve ->(obj, args, ctx) { CHEESES.values + MILKS.values }
end

field :allEdibleAsMilk, types[EdibleAsMilkInterface] do
resolve ->(obj, args, ctx) { CHEESES.values + MILKS.values }
end

field :error do
description "Raise an error"
type GraphQL::STRING_TYPE
Expand Down

0 comments on commit 75497f2

Please sign in to comment.