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) endend
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] } endend
# 認証に失敗すると 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) endend
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] endend
# 認証に失敗すると 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) endend
# 認証に失敗すると GraphQL::Guard::NotAuthorizedError がraiseされる雑感
この件はgemのREADMEを改善したほうがよさそう。また、なにかもうちょっといい方法があれば教えてください。