GraphQLで独自Scalar型を定義する時に気をつけること

GraphQL-Rubyでは、IntFloatStringBoolean,IDといったビルドイン以外にISO8601DateTimeISO8601Date型が提供されています。

また、独自Scalar型を定義することができ、その方法はドキュメントに記載されています。

class Types::Url < Types::BaseScalar
  def self.coerce_input(input_value, context)
    url = URI.parse(input_value)
    if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS)
      url
    else
      raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL"
    end
  end

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

上記のように独自Scalar型を定義できますが、graphql-specから一点忘れてはいけないことがあります。

それは、InputとResultでバリデーションを行い適切なエラーを出す必要があるということです。

Types::Urlでは、self.coerce_inputメソッドでGraphQL::CoercionErrorを出力していることがわかります。

また、Resultではruby_value.to_sとあり一見するとエラーを出していないように思われます。to_sメソッドは、ほぼ全てRubyオブジェクトに適応できてしまうため結果がIntegerであるパターンを考えます。

シンプルな独自ScalerであるBigIntがGraphQL-Rubyに実装されています。もし、Arrayが与えられた時、value.to_iを実行するとNoMethodErrorが出力されます。rescueなどで拾ってやる必要はありそうですが、Resultでもバリデーションをチェックすることが必要であるとわかります。

lib/graphql/types/big_int.rb

# frozen_string_literal: true

module GraphQL
  module Types
    class BigInt < GraphQL::Schema::Scalar
      description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string."

      def self.coerce_input(value, _ctx)
        Integer(value)
      rescue ArgumentError
        nil
      end

      def self.coerce_result(value, _ctx)
        value.to_i.to_s
      end
    end
  end
end

まとめ

GraphQLで独自Scalerを定義する際は、バリデーションをしてエラーを出すべきだ

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

  • 作者:Eve Porcello,Alex Banks
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2019/11/13
  • メディア: 単行本(ソフトカバー)