RubyでStringのサブクラスをprotobufに渡せるようにした話

毎年機会を見つけては OSS に Contribute したいなと思っているのですが、今年もなかなか思うようには貢献できませんでした。
今回は protocolbuffers/protobuf に投げた Pull Request について紹介します。

TL;DL

  • Protocol Buffers の Ruby 実装として Google が公式に実装している protocolbuffers/protobuf がある
  • Protocol Buffers の string フィールドに対して Ruby の String を渡すことができるが String のサブクラスは受つけず、ランタイムエラーが出てしまっていた
  • String のサブクラスも受け取るように提案した PR が受け入れられた 🎉

protocolbuffers/protobuf とは

前提として Protocl Buffers とは Google が提案・利用しているデータシリアライゼーションフォーマットで、ILD を提供しています。
主に gRPC による通信で利用されています。protocolbuffers/protobufGoogle によって実装された公式ライブラリであり、2023年12月現在で C++ / C# / Dart / Go / Java / Kotlin / Objective-C / PHP / Python / Ruby 向けのライブラリを提供しています。 このライブラリを使うことで各言語で任意のオブジェクトを Protocol Buffers の形式にシリアライズすることができます。

今回は社内のマイクロサービス間通信をおこなっている箇所で Rails の gRPC Server を実装したところ、後述する問題に遭遇しました。

何が起きていたか

Protocol Buffers に定義されているスカラデータ型 は15種類あります。 具体的には符号付/符号なし整数型と浮動小数点型、そして文字列型、ブーリアン型、バイト型です。

今回 Ruby の protobuf ライブラリの文字列型(string*1 ) のフィールドに対して RailsActiveSupport::SafeBuffer を渡したところ以下のようなエラーが出てしまいました。

Google::Protobuf::TypeError: Invalid argument for string field 'foo_field' (given ActiveSupport::SafeBuffer).

どうあるべきか

ActiveSupport::SafeBufferXSS 対策として提供されているクラスで、HTML として出力する際に安全であるとマークされた文字列を表すためのクラスです。
実装を見てみるとわかるのですが String を継承したサブクラスとなっています。

module ActiveSupport # :nodoc:
  class SafeBuffer < String
    # ...
  end
end

github.com

String のサブクラスが string フィールドに渡せないのはリスコフの置換原則に反しているため修正するのが適切だと考えました。

修正

Ruby の protobuf ライブラリは FFI を用いて実装されており、本体はC言語で書かれています。今回の修正対象もC言語でしたが、運よく適切な関数 `rb_obj_is_kind_of を見つけることができ無事実装できました。
困りポイントとしては fork したリポジトリ上でうまく CI を動かすことができず、本家に Pull Request を投げて実行してもらうまでは CI 通るか確認ができなかったのが大変でした。

github.com その後 Reviewer と何度かやり取りがあり、Pull Request 上は Closed になっていますが 34908e2 で無事取り込まれました 🎉

感想

今回の内容とあまり関係ないのですが、今までは Pull Request の Description やコメントでのやり取りなど英語でのコミュニケーションに課題を感じていてあまり積極的になれませんで。
しかし ChatGPT をはじめとする AI を使い、AI に一度レビューしてもらうことである程度自信をもってやり取りすることができるようになったと感じました。
これを機に2024年はもっと積極的に OSS 活動していきたいです。

*1:この記事中では String と表記した場合は Ruby の文字列クラス、string と表記した場合は Protocol Buffers の文字列型とします