graphql-guardとポリシーオブジェクトをgraphql-ruby 1.8で使うための方法
これの続きです。
問題
graphql-guardはインラインでポリシーを書く方法の他に、ポリシーオブジェクトを定義して GraphQL::Guard.new
に渡すという方法があります。使いかたはREADMEに書いてあるとおりです。
class GraphqlPolicy
RULES = {
Types::QueryType => {
viewer: ->(obj, args, ctx) { !ctx[:current_user].nil? }
}
}
def self.guard(type, field)
type.introspection? ? ->(_, _, _) { true } : RULES.dig(type, field)
end
end
MySchema = GraphQL::Schema.define do
query Types::QueryType
use GraphQL::Guard.new(policy_object: GraphqlPolicy)
end
Types::QueryType = GraphQL::ObjectType.define do
name "Query"
field :viewer, !Types::UserType do
resolver ->(obj, args, ctx) { ctx[:current_user] }
end
end
# 認証に失敗すると GraphQL::Guard::NotAuthorizedError がraiseされる
で、graphql-ruby 1.8でclass-based APIを使ってスキーマを書き直すと、残念ながらこのポリシーオブジェクトは動かなくなります。
class GraphqlPolicy
RULES = {
Types::Query => {
viewer: ->(obj, args, ctx) { !ctx[:current_user].nil? }
}
}
def self.guard(type, field)
type.introspection? ? ->(_, _, _) { true } : RULES.dig(type.name, field)
end
end
class MySchema < GraphQL::Schema
query Types::Query
use GraphQL::Guard.new(policy_object: GraphqlPolicy)
end
class Types::Query < Types::BaseObject
field :viewer, Types::User, null: false
def viewer
context[:current_user]
end
end
# 認証に失敗すると GraphQL::Guard::NotAuthorizedError がraiseされず、
# viewerのresolverの結果がnilになるので、次のエラーになってしまう
# {
# "data": null,
# "errors": [
# {
# "message": "Cannot return null for non-nullable field Query.viewer"
# }
# ]
# }
解決策
上の問題の原因はHash GraphqlPolicy::RULE
のキーの等価比較にあります。
graphql-guardではフィールドのinstrumentationを使っており、GraphqlPolicy.guard
に渡る type
は、graphql-guardの差し込むinstrumentationで取得した、class-based API以前のオブジェクト型を表す GraphQL::ObjectType
のインスタンスです。一方、RULE
のキーはclass-based APIで導入された GraphQL::Schema::Object
のインスタンスなので、クラスがそもそも一致していません。なので、これを GraphQL::ObjectType
のような古いほうのクラスのインスタンスに変換するためのメソッド #to_graphql
を呼ぶ必要があります。
また、GraphQL::ObjectType
では eql?
をオーバーライドしていないので、Hashのキーの比較にはオブジェクトIDが使われます。ここで、#to_graphql
は内部で GraphQL::ObjectType.new
しているので、これで作ったインスタンスと RULE
のキーになっているインスタンスはオブジェクトIDが異なってしまいます。よって、キーが一致せず、RULE
からguard Procを見つけられないので、望むような動作をしなくなっています。
GraphqlPolicy
を次のように定義し直せば動きます。GraphQLの型をキーとするのは諦めて、その型の名前 name
をキーとすることで、Hashのキー探索がうまくハマるようにしています。
class GraphqlPolicy
RULES = {
Types::Query.to_graphql.name => {
viewer: ->(obj, args, ctx) { !ctx[:current_user].nil? }
}
}
def self.guard(type, field)
type.introspection? ? ->(_, _, _) { true } : RULES.dig(type.name, field)
end
end
# 認証に失敗すると GraphQL::Guard::NotAuthorizedError がraiseされる
雑感
この件はgemのREADMEを改善したほうがよさそう。また、なにかもうちょっといい方法があれば教えてください。