graphql-rubyでCustom Scalarsを定義してみる

こんにちは!kossyです!




今回はgraphql-rubyでCustom Scalarsを定義する方法について、備忘録としてブログに残してみたいと思います。




環境
Ruby 2.6.8
Rails 6.0.4
MacOS BigSur
graphql-ruby 1.12.19



CustomScalarsとは

graphqlにはデフォルトで組み込まれているいくつかのscalar値があります。

String, like a JSON or Ruby string
Int, like a JSON or Ruby integer
Float, like a JSON or Ruby floating point decimal
Boolean, like a JSON or Ruby boolean (true or false)
ID, which a specialized String for representing unique object identifiers
ISO8601DateTime, an ISO 8601-encoded datetime
ISO8601Date, an ISO 8601-encoded date
JSON, ⚠ This returns arbitrary JSON (Ruby hashes, arrays, strings, integers, floats, booleans and nils). Take care: by using this type, you completely lose all GraphQL type safety. Consider building object types for your data instead.
BigInt, a numeric value which may exceed the size of a 32-bit integer

出典: GraphQL - Scalars

上記以外でも、scalarsを開発者が独自に定義することができます。それがCustomScalarsです。

CustomScalarsの定義

CustomScalarsを定義するには、Types::BaseScalarを継承したクラスを作成する必要があります。

module Types
  class PhoneNumber < Types::BaseScalar
    description 'PhoneNumber'

    def self.coerce_input(input_value, _context)
      if input_value.match?(/\A\d{10,11}\z/)
        input_value
      else
        raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid Phone Number."
      end
    end

    def self.coerce_result(ruby_value, context)
      ruby_value.to_s
    end
  end
end

このように定義を行い、

module Types
  class UserInfoInputType < Types::BaseInputObject
    argument :phone_number, Types::PhoneNumber, required: true
  end
end

のようにargument等で指定することによって適用することができます。

graphiql等のGrapQL Clientで試した結果は以下です。

{
  "errors": [
    {
      "message": "Variable $userInfoInput of type UserInfoInputForm! was provided invalid value for userInfo.phoneNumber (\"タイトル\" is not a valid Phone Number.)",
      "locations": [
        {
          "line": 1,
          "column": 39
        }
      ],
      "extensions": {
        "value": {
          "userInfo": {
            "phoneNumber": "タイトル"
          }
        },
        "problems": [
          {
            "path": [
              "userInfo",
            ],
            "explanation": "\"タイトル\" is not a valid Phone Number.",
            "message": "\"タイトル\" is not a valid Phone Number."
          }
        ]
      }
    }
  ]
}

「タイトル」という文字列はInvaildだという例外が得られました。

正しい文字をargumentとして渡した場合を見てみます。

{
  "data": {
    "updateUserInfo": {
      "userInfo": {
        "phoneNumber": "09012345678"
      }
    }
  }
}

例外が起こらずに処理が完了しました。


まとめ

CustomScalarを使えば、プリミティブな型(StringやInt等)を使うよりもより厳密にデータを扱うことができそうです。