🎃

GraphQL RubyでバリデーションエラーをUnion Typeで返す

2023/06/28に公開

こんにちはmofmofでエンジニアをしているshwldです。

graphqlでエラーを返す方法はいくつかあり、それぞれメリデメありますが、Unionで返すのが好みです。

例えば、以下のようにmutationで実行時のバリデーションエラーを共通のフォーマットで返すことを考えてみました。

mutation createPost($input: CreatePostInput!) {
  createPost(input: $input) {
    result {
      ... on Post {
        id
        name
      }
      ... on InvalidObject {
        errors {
          fullMessage
        }
      }
    }
  }
}

このクエリを見るとわかるようにCreate, Updateを行うmutationのエラーを共通のInvalidObjectで受け取ることができます。

この方法を取ると、想定済のエラーのパターンと、エラーオブジェクトを型付で知ることができるため、実装時の時点でそれぞれのエラーパターンへの対応コードを書きやすくなると思います。

ということで、union型を使ったエラーの内、バリデーションエラーの型付をGraphQL Rubyでやってみました。

mutationファイル

app/graphql/mutations/create_post.rb
module Mutations
  class CreatePost < BaseMutation
    field :result, Mutations::CreatePostResult, null: false

    argument :title, String, required: true
    argument :body, String, required: true

    def resolve(title:, body:)
      post = Post.create(title:, body:)

      { result: post }
    end
  end
end

mutationはこのようになります。
resultというフィールドに Mutations::CreatePostResult を指定しているのがポイントです。

mutation resultファイル

app/graphql/mutations/create_post_result_type.rb
class Mutations::CreatePostResult < Types::BaseUnion
  possible_types Types::Objects::PostType, Types::Objects::InvalidObjectType

  def self.resolve_type(object, _context)
    if object.invalid?
      Types::Objects::InvalidObjectType
    else
      Types::Objects::PortfolioType
    end
  end
end

resultファイルはmutations配下においています。mutationではないのでちょっと気持ち悪いですが、命名ルールを揃えることで、ファイルがmutationと隣になるので、扱いやすくなります。

バリデーション失敗したモデルのためのオブジェクト

app/graphql/types/objects/invalid_object_type.rb
module Types::Objects
  # バリデーション失敗したモデル用のオブジェクト
  class InvalidObjectType < Types::BaseObject
    field :id, ID, null: true
    field :model, String, null: false
    def model
      object.class.name
    end

    field :errors, [Types::Objects::ValidationErrorType], null: false

    field :full_messages, [String], null: false
    def full_messages
      object.errors.full_messages
    end
  end
end

InvalidObjectTypeはinvalid?なモデルの表示を行うオブジェクトです。

バリデーションエラーのためのオブジェクト

app/graphql/types/objects/validation_error_type.rb
module Types::Objects
  # バリデーションエラー用のオブジェクト
  class ValidationErrorType < Types::BaseObject
    field :attribute, String, null: false
    field :type, String, null: false

    field :message, String, null: false
    field :full_message, String, null: false
  end
end

attribute名をEnumにしたい気持ちもありますが、バリデーションエラーを型付きで取得できるようになりました。やったね!

以上、個人開発しているサービスで試した記録でした。

この記事は、株式会社mofmofの「水曜日の個人開発」にサポートされています。
https://indie-dev.mof-mof.co.jp

mofmof inc.

Discussion