Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Generating OpenAPI schema from serializers thro...

Generating OpenAPI schema from serializers throughout the Rails stack - Kyobashi.rb #5

While we recommend the schema-first approach to OpenAPI in our “Let there be docs! A documentation-first approach to Rails API development” guide in our blog, the opposite approach has its place in the wild too.

Let’s see how it can be achieved with sometimes unintended usage of various tools (including martian ones!).

Andrey Novikov

February 26, 2025
Tweet

More Decks by Andrey Novikov

Other Decks in Programming

Transcript

  1. Generating OpenAPI schema from serializers throughout the Rails stack Andrey

    Novikov, Evil Martians Kyobashi.rb #5, 2025-02-26 シリアライザーから Railsスタック全体を参考して OpenAPIスキーマの生成方法
  2. About me Andrey Novikov ノヴィコフ・アンドレイ Back-end engineer at Evil Martians

    イービルマーシャンズのバックエンドエンジニア Ruby, Go, PostgreSQL, Docker, k8s… Living in Suita, Osaka for 2 years 2年間以上、大阪府吹田市に住んでいます Love to ride mopeds, motorcycles, and cars over Japan 原付も、大型バイクも、車で日本を旅するのが好き 自己紹介
  3. If you have an API you need the schema! For

    SPA frontends, mobile apps, etc. SPAフロントエンド、モバイルアプリなど For documentation, validation, and code generation ドキュメント、リクエストとレスポンスの検証、コード生成のため Even if you don’t have external clients 外部クライアントがいなくても APIがある場合はスキーマが必要です!
  4. Different approaches / 異なるアプローチ Schema First / スキーマファースト Write OpenAPI

    spec first 最初に OpenAPIスキーマを書く Ensure that implementation matches the spec 実装がスキーマに一致していることを確認 More organized, requires more upfront planning より整理されているが、事前の計画が必要 Recommended in our blog post 当社のブログ記事で推奨 Blog post
  5. Different approaches / 異なるアプローチ Implementation First / 実装ファースト Ensures that

    spec matches the implementation スキーマが実装に一致していることを確認 Simpler for small teams 小規模チームに適している Faster iteration より速い反復開発 Today’s topic 今日の話題
  6. Typical Rails API stack Database: stores data and has types

    データを保存し、データ型の情報を持つ Models: defines relationships and introspects database モデルは関係を定義し、データベースを調査 Controllers: handle requests and responses コントローラーはリクエストとレスポンスを処理 Serializers: generates JSON for API responses from models シリアライザーはモデルから APIレスポンスの JSONを生成 典型的な Rails APIスタック
  7. Existing Tools Review Swagger Blocks ❌ Abandoned 開発が停止 ⚠️ Limited

    OpenAPI 3.0 support OpenAPI 3.0の限定的なサポート Apipie ❌ OpenAPI 2.0 only OpenAPI 2.0のみ ❌ Abandoned 開発が停止 RSwag ✅ Actively maintained アクティブに保守されている ✅ OpenAPI 3 support OpenAPI 3をサポート 既存ツールのレビュー
  8. RSwag: Pros and Cons Pros / 長所 ✅ Works well

    正常に動作 ✅ Maintained メンテナンスされている ✅ Good integration 良好な統合 Cons / 短所 ❌ No separate type definitions 個別の型定義がない ❌ Manual template maintenance テンプレートの手動管理が必要 ❌ $ref support limitations $refサポートの制限 RSwag:長所と短所
  9. Typical RSwag test 典型的な RSwagテスト schema type: :object, properties: {

    id: { type: :integer }, full_name: { type: :string }, bar: { type: :object, properties: { … }} }, required: [ 'id', 'full_name' ] # spec/requests/api/v1/foos_spec.rb RSpec.describe "/api/v1/foos", openapi_spec: "v1/schema.yml" do path "/v1/foos/{id}" do get "Get a Foo" do parameter name: :page, in: :query, schema: { type: :integer, default: 1 }, require response "200", "A successful response" do run_test! end end
  10. ❌ No separate type definitions What if we could get

    type info from serializers… シリアライザーからデータ型情報を取得できたらいいな … 個別の型定義がない class FooSerializer < ActiveModel::Serializer attribute :id # Type can be inferred from the model Foo attribute :full_name do # Need to declare somehow first_name + ' ' + last_name end has_one :bar, serializer: BarSerializer end
  11. Introducing Typelizer gem Generates TypeScript type definitions TypeScriptの型定義を生成 Works with

    several serializer libraries いくつかのシリアライザーライブラリと連携 ActiveModelSerializer Alba Made by a martian Svyatoslav @skryukov 火星人のスヴャトスラフさんが作った gem But how it can help us with OpenAPI schema? しかし、 OpenAPIスキーマにどのように役立つのか? Typelizer gemの紹介 Typelizer gem
  12. Let’s hack around and find out! Can we extract and

    re-use type information without generating TypeScript defs? TypeScriptの定義を生成せずに、型情報を抽出して再利用する方法できるのか? ハックをやってみて、どうなってしまうか見てみよう!
  13. Step 1: Add annotations to serializers スキーマにアノテーションを追加 class FooSerializer <

    ActiveModel::Serializer include Typelizer::DSL attribute :id # Will be inferred from the model Foo typelize :string attribute :full_name do first_name + ' ' + last_name end has_one :bar, serializer: BarSerializer end
  14. Step 2: Define RSwag schema template RSwag用のスキーマテンプレートを定義 # spec/swagger_helper.rb RSpec.configure

    do |config| config.openapi_specs = { "schema.yml" => { openapi: "3.1.0", paths: {}, # RSwag will fill this in components: { schemas: { Typelizer::Generator.new.interfaces.to_h do |interface| [ interface.name, # Magic is here ] end } } } } end
  15. Step 3: Convert typelizer data to OpenAPI Typelizerのデータを OpenAPIスキーマに変換 {

    type: :object, properties: interface.properties.to_h do |property| definition = case property.type when Typelizer::Interface { :$ref => "#/components/schemas/#{property.type.name}" } else { type: property.type.to_s } end definition[:nullable] = true if property.nullable definition[:description] = property.comment if property.comment definition[:enum] = property.enum if property.enum definition = { type: :array, items: definition } if property.multi [ property.name, definition ] end, required: interface.properties.reject(&:optional).map(&:name) }
  16. Step 4: Write RSwag specs as usual 通常通り RSwagスペックを記述 schema

    type: :array, items: { "$ref" => "#/components/schemas/Foo" } # spec/requests/api/v1/foos_spec.rb require "swagger_helper" RSpec.describe "/api/v1/foos", openapi_spec: "v1/schema.yml" do path "/v1/foos" do get "List Foos" do produces "application/json" description "Returns a collection of foos" parameter name: :page, in: :query, schema: { type: :integer, default: 1 }, require response "200", "A successful response" do run_test! end end
  17. Hint: AI can rewrite specs to RSwag If there are

    already controller or request RSpec tests すでにコントローラーまたはリクエスト RSpecテストがある場合 Claude AI can rewrite them to RSwag specs Claude AIはそれらを RSwagスペックに書き換えます Though amount of manual work for re-checking is qute high ただし、再確認のための手作業がかなり多い ヒント: AIはスペックを RSwagにうまく書き換えます
  18. And voila! そして、できあがり! $ bundle exec rails rswag paths: /v1/foos:

    get: responses: '200': content: application/json: schema: type: array items: $ref: '#/components/schemas/Foo' components: schemas: Foo: type: object properties: id: type: integer full_name: type: string bar: $ref: '#/components/schemas/Bar'
  19. The recipe is Generate schema definitions from serializers シリアライザーから TypeScriptの型を生成

    Describe endpoint schemas as RSwag specs エンドポイントスキーマを RSwagスペックとして記述 Reference schema definitions using $ref in specs スペックで $ref を使用してスキーマ定義を参照 レシピは
  20. Validating generated schema Use Spectral to validate OpenAPI schema OpenAPIスキーマを検証するために

    Spectralを使用 Add following check to your Github Actions Github Actionsに以下のチェックを追加 - uses: stoplightio/spectral-action@latest with: file_glob: 'openapi/**/schema.yaml' spectral_ruleset: 'openapi/.spectral.yml' 生成されたスキーマの検証 Spectral
  21. Ensuring schema is re-generated Add following check to your Github

    Actions using plain git commands 次のチェックを、プレーンな gitコマンドを使用して Github Actionsに追加 - name: Re-generate OpenAPI spec and check if it is up-to-date run: | bundle exec rails rswag if [[ -n $(git status --porcelain openapi/) ]]; then echo "::error::OpenAPI documentation is out of date. Please run `rails rswag` locally and comm git status git diff exit 1 fi スキーマが再生成されることを確認
  22. Detect breaking changes Use oasdiff to get a diff between

    two OpenAPI schemas oasdiffを使用して 2つの OpenAPIスキーマの差分を取得 docker run --rm -t -v $(pwd):/specs:ro tufin/oasdiff changelog \ old_schema.yml new_schema.yml -f html > oas_diff.html oasdiff
  23. Is it used in production? Yes, at Whop.com! Whop is

    rapidly growing social commerce platform, development speed is their top priority, and ability to generate OpenAPI schema from serializers is a major pain relief. production環境で使われているのか?
  24. Thanks @envek, this setup is pretty sweet, much better than

    manually editing a massive text file. — Diego Figueroa, staff engineer at Whop.com
  25. Thank you! @Envek @Envek @[email protected] @envek.bsky.social github.com/Envek @evilmartians @evilmartians @[email protected]

    @evilmartians.com evilmartians.com Our awesome blog: evilmartians.com/chronicles! ご清聴ありがとうございました!