<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Schemathesis on Rindrics Mumbles</title>
    <link>https://rindrics.com/tags/schemathesis/</link>
    <description>Schemathesis</description>    
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Sun, 12 Jul 2026 00:00:00 +0900</lastBuildDate>
    <atom:link href="https://rindrics.com/tags/schemathesis/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>API 契約駆動で開発できるようにした</title>
      <link>https://rindrics.com/posts/stock-assess-api-contract/</link>
      <pubDate>Sun, 12 Jul 2026 00:00:00 +0900</pubDate>
      
      <guid>https://rindrics.com/posts/stock-assess-api-contract/</guid>
      <description>&lt;p&gt;Open API 形式の API 仕様を基礎として開発できるような体制を整えた。&lt;/p&gt;
&lt;h2 id=&#34;背景と問題点&#34;&gt;背景と問題点&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;../stock-assess-plumber/&#34;&gt;前回記事&lt;/a&gt;では、frasyr をモノリスの API サーバーとして動かすための第一歩として、ひとまず plumber でラップしてみた。
当然 frasyr を API 経由で使えるようになったものの、一方で後に控える契約層やフロントの開発のことを考えると、依存の向きに致命的な課題があった。
というのも、前回は plumber をひとまず触ってみるのが目的だったので、frasyr を API 化できること、また API ドキュメントを Swagger UI で閲覧できることを確認して一区切りとしていた。
問題は API ドキュメントの確認のほうで、これをやりたいために前記事では plumber の &lt;code&gt;getApiSpec()&lt;/code&gt; を利用しており、将来的には契約の実体としたい OpenAPI 形式の仕様ファイルの方がコード&lt;strong&gt;から生成&lt;/strong&gt;されていた。
本来あるべき姿としては、モノリスの API は契約&lt;strong&gt;に準拠&lt;/strong&gt;している必要があるが（下図左）、API から仕様を生成したのでは、依存の向きが逆になってしまっている（下図右）。
これでは「契約」という概念を持ち込んだ意味がない。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://rindrics.com/images/2026-07-12-api-contract/generated-contract.webp&#34;
    alt=&#34;前回記事の時点では、契約のほうが API から生成されてしまっていた&#34;&gt;&lt;figcaption&gt;
      &lt;p&gt;前回記事の時点では、契約のほうが API から生成されてしまっていた&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;将来的には上図左の &amp;ldquo;Contract layer&amp;rdquo; を契約から生成したいので、次のステップとして下記 2 点に取り組むこととした:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open API 形式の API 仕様ファイルを契約の SSOT とする&lt;/li&gt;
&lt;li&gt;前回作ったモノリスサービスがこの契約に違反しないような仕組みを確立する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;やったこと&#34;&gt;やったこと&lt;/h2&gt;
&lt;p&gt;ざっくりこんな感じ:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API 仕様をバージョン管理&lt;/li&gt;
&lt;li&gt;Contract test の導入&lt;/li&gt;
&lt;li&gt;リクエスト・レスポンスの検証&lt;/li&gt;
&lt;li&gt;API ドキュメントの配信&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;それぞれ簡単にメモしておく。&lt;/p&gt;
&lt;h3 id=&#34;api-仕様をバージョン管理&#34;&gt;API 仕様をバージョン管理&lt;/h3&gt;
&lt;p&gt;Open API 形式の仕様ファイルを、フロント&amp;ndash;バック間における契約の SSOT としたい。
これまでは plumber から生成される &lt;code&gt;openapi.json&lt;/code&gt; を &lt;code&gt;.gitignore&lt;/code&gt; していたがこれをやめ、バージョン管理を始めた。&lt;/p&gt;
&lt;p&gt;これから仕様を手で書いていくとなると JSON 形式だと少々厳しそうなので、ついでに YAML に変換した:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;yq -p json -o yaml &lt;span style=&#34;color:#2aa198&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt; schema/openapi.json &amp;gt; schema/openapi.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;contract-test-の導入&#34;&gt;Contract test の導入&lt;/h3&gt;
&lt;p&gt;frasyr の API サーバーを契約通りに実装できているかを確認するために Contract test を導入した。
テスト用ツールには当初 Claude に薦められるがままに &lt;a href=&#34;https://dredd.org/en/latest/&#34;&gt;Dredd&lt;/a&gt; を使いかけていたが、&lt;a href=&#34;https://github.com/apiaryio/dredd&#34;&gt;repo&lt;/a&gt; の方を見てみたら &lt;code&gt;public archive&lt;/code&gt; になっていたので &lt;a href=&#34;https://schemathesis.io/&#34;&gt;Schemathesis&lt;/a&gt; を使うことにした。
他の候補として一応 &lt;a href=&#34;https://stoplight.io/open-source/prism&#34;&gt;Prism&lt;/a&gt; も検討したが、一人で開発するので Prism の売りの一つであるモックサーバーはそんなに魅力的に映らなかった。&lt;/p&gt;
&lt;p&gt;Contract test としてのコア機能はこんな感じ:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Examples: API 仕様の example に valid なリクエスト例を書いておくことで、正常系のリクエストが生成される&lt;/li&gt;
&lt;li&gt;Fuzzing: API 仕様をもとに、正常系・異常系のリクエストを生成する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Examples はまぁいいとして、Fuzzin test がなかなかおもしろかった。
Schemathesis が生成した、仕様的に valid なリクエストに対して &lt;code&gt;4XX&lt;/code&gt; や &lt;code&gt;5XX&lt;/code&gt; が返った場合、テストが失敗してくれるので、エラーハンドリングが甘かったり、仕様記述が漏れていたりといったミスに気付ける。
また逆に、仕様的に invalid なリクエストに対して &lt;code&gt;2XX&lt;/code&gt; 系が返った場合にも、エラーハンドリングや仕様記述のミスに気づけるというわけ。&lt;/p&gt;
&lt;p&gt;あとは &lt;code&gt;Reproducible Failures&lt;/code&gt; という機能が地味に便利で、期待と異なるレスポンスが返った時に、再現用の &lt;code&gt;curl&lt;/code&gt; リクエストを表示してくれた:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;=================================== FAILURES ===================================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_________________________________ POST /v0/vpa _________________________________
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1. Test Case ID: FZf6yc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- Server error
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[500] Internal Server Error:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    `{&amp;#34;error&amp;#34;:&amp;#34;Internal validation error&amp;#34;}`
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Reproduce with:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    curl -X POST -H &amp;#39;Content-Type: application/json&amp;#39; -d &amp;#39;{&amp;#34;data&amp;#34;: {&amp;#34;caa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_caa.csv&amp;#34;, &amp;#34;waa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_waa.csv&amp;#34;, &amp;#34;maa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_maa.csv&amp;#34;}, &amp;#34;params&amp;#34;: {&amp;#34;m&amp;#34;: 0.5, &amp;#34;fc_year&amp;#34;: [2015, 2016, 2017], &amp;#34;tf_year&amp;#34;: [2015, 2016], &amp;#34;term_f&amp;#34;: &amp;#34;max&amp;#34;, &amp;#34;stat_tf&amp;#34;: &amp;#34;mean&amp;#34;, &amp;#34;pope&amp;#34;: true, &amp;#34;tune&amp;#34;: false, &amp;#34;p_init&amp;#34;: 0.5}}&amp;#39; http://host.docker.internal:31502/v0/vpa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    st replay FZf6yc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;これだけでも、サーバー側のログと照らし合わせることで問題箇所の特定が捗った。&lt;/p&gt;
&lt;p&gt;ちなみに、contract test が参照すべき Open API 仕様はローカルファイルパスだけでなく URL 指定もできるので、provider&amp;ndash;consumer 関係の複数チームでも運用できそう。
本プロジェクトではひとまず monorepo でやるのでローカルパス指定でいく。&lt;/p&gt;
&lt;h4 id=&#34;dev-環境および-ci-におけるテスト設定のポイント&#34;&gt;Dev 環境および CI におけるテスト設定のポイント&lt;/h4&gt;
&lt;h5 id=&#34;dev-環境&#34;&gt;Dev 環境&lt;/h5&gt;
&lt;p&gt;開発中は検証サイクルを早くしたいので、image build を介さず、ホストの R ランタイムで直接起動した plumber server に対してテストするようにした:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Makefile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;run:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	VALIDATE_RESPONSE=true \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    LOG_LEVEL=$(LOG_LEVEL) \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    OPENAPI_SPEC_PATH=../../schema/openapi.yaml \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PLUMBER_PORT=$(PLUMBER_DEV_PORT) \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Rscript run.R
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test-local:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	docker run --rm \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  -v $$(pwd)/../../schema:/schema \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  -v $$(pwd)/../../schemathesis.toml:/workspace/schemathesis.toml \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  -v $$(pwd)/../..:/workspace \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  -w /workspace \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  ghcr.io/schemathesis/schemathesis:stable \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  run /schema/openapi.yaml \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  --url http://host.docker.internal:$(PLUMBER_DEV_PORT) \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 	  --coverage-format html --coverage-report-html-path ./schema-coverage.html
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;本プロジェクトは現時点ではクラウド環境を作っておらず、compose で立ち上げるコンテナを暫定的に本番環境とみなしている。
このコンテナを使った動作確認も並行して行う機会があったので、dev 環境（ホストの R ランタイムで立ち上げた plumber サーバー）のポートは &lt;code&gt;PLUMBER_DEV_PORT&lt;/code&gt; として別に割り当てた。&lt;/p&gt;
&lt;p&gt;ちなみにコンテナ版の Schemathesis からホストのエンドポイントを叩くので、base URL には &lt;code&gt;host.docker.internal&lt;/code&gt; の利用が必須。&lt;/p&gt;
&lt;h4 id=&#34;ci&#34;&gt;CI&lt;/h4&gt;
&lt;p&gt;CI ではより本番環境に近い状態でテストしたいので、API サーバーはコンテナ版を使うようにした。
ただしデリバリー済みのイメージを使ってしまうと PR 時などの CI として意味がないので、ワークフロー内でイメージをビルドしている。&lt;/p&gt;
&lt;p&gt;CI で利用しているテストコマンドは下記:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Makefile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	docker run --rm \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  --network $(DOCKER_NETWORK) \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  -v $$(pwd)/../../schema:/schema \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 	  -v $$(pwd)/../../schemathesis.toml:/workspace/schemathesis.toml \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  -v $$(pwd)/../..:/workspace \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  -w /workspace/.schemathesis \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  ghcr.io/schemathesis/schemathesis:stable \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  run /schema/openapi.yaml \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  --url http://monolith:$(CONTAINER_PORT) \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	  --coverage-format html --coverage-report-html-path ./schema-coverage.html
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;dev 環境とパスがちょっと違うのは、レポート書き出し時に permission error になったのでディレクトリを掘って逃げた形跡。
あとは compose で立ち上げたサービスを叩くので docker network を使うようになっており、エンドポイントはサービス名 monolith で解決できる。&lt;/p&gt;
&lt;h3 id=&#34;リクエストレスポンスの検証&#34;&gt;リクエスト・レスポンスの検証&lt;/h3&gt;
&lt;p&gt;リクエストの検証に関しては、Schemathesis Fuzzy test のおかげで突貫ながらもそこそこ進められた。
&lt;code&gt;additionalProperties&lt;/code&gt; についてはやや迷ったのだが、今のところ BFF しか利用しない内部 API として作るので &lt;code&gt;false&lt;/code&gt; にしておいた。&lt;/p&gt;
&lt;p&gt;レスポンスの検証は dev 環境のみで有効になるようにした。
これはちょうど最近読んだ『データ指向プログラミング&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;』のプラクティスを参考にしている。&lt;/p&gt;
&lt;h3 id=&#34;api-ドキュメントの配信&#34;&gt;API ドキュメントの配信&lt;/h3&gt;
&lt;p&gt;せっかく API 仕様を管理していくので、API ドキュメントをどこかにホストしたい。
&lt;a href=&#34;https://scalar.com/&#34;&gt;Scalar&lt;/a&gt; というのがよさそうだったのでこれを使うことにした。
全然真面目に選定していないが、この手のツールは Open API document を読ませるだけなので、いざ問題が生じたとしても乗り換えコストは小さいはず。
ホスト先はいつも使っている Netlify にした。&lt;/p&gt;
&lt;p&gt;ドメインについては、下手に stock-assessment とかを当ててしまうと、方向転換を新規プロジェクトでやりたくなったときに不便なので、プロジェクト名からなるドメインにした: &lt;a href=&#34;https://api-docs.yoshimoto-samonji.rindrics.com/#tag/vpa&#34;&gt;https://api-docs.yoshimoto-samonji.rindrics.com/#tag/vpa&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;ハマったところ&#34;&gt;ハマったところ&lt;/h2&gt;
&lt;h3 id=&#34;sechemathesis-が生成した-query-method-によるテスト失敗&#34;&gt;Sechemathesis が生成した QUERY method によるテスト失敗&lt;/h3&gt;
&lt;p&gt;Schemathesis が（今年の 6 月に出たばかりの）&lt;a href=&#34;https://datatracker.ietf.org/doc/rfc10008/&#34;&gt;&lt;code&gt;QUERY&lt;/code&gt; method&lt;/a&gt; を投げてくれるのには困った。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;==================================== ERRORS ====================================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_________________________________ POST /v0/vpa _________________________________
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Network Error
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Connection failed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Connection aborted. Remote end closed connection without response
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Reproduce with:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    curl -X QUERY -H &amp;#39;Content-Type: application/json&amp;#39; -d &amp;#39;{&amp;#34;data&amp;#34;: {&amp;#34;caa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_caa.csv&amp;#34;, &amp;#34;waa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_waa.csv&amp;#34;, &amp;#34;maa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_maa.csv&amp;#34;}, &amp;#34;params&amp;#34;: {&amp;#34;m&amp;#34;: 0.5, &amp;#34;fc_year&amp;#34;: [2015, 2016, 2017], &amp;#34;tf_year&amp;#34;: [2015, 2016], &amp;#34;term_f&amp;#34;: &amp;#34;max&amp;#34;, &amp;#34;stat_tf&amp;#34;: &amp;#34;mean&amp;#34;, &amp;#34;pope&amp;#34;: true, &amp;#34;tune&amp;#34;: false, &amp;#34;p_init&amp;#34;: 0.5}}&amp;#39; http://host.docker.internal:31502/v0/vpa
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;確かに繋がらない:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl &lt;span style=&#34;color:#719e07&#34;&gt;-&lt;/span&gt;v &lt;span style=&#34;color:#719e07&#34;&gt;-&lt;/span&gt;X QUERY &lt;span style=&#34;color:#719e07&#34;&gt;-&lt;/span&gt;H &lt;span style=&#34;color:#2aa198&#34;&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#719e07&#34;&gt;-&lt;/span&gt;d &lt;span style=&#34;color:#2aa198&#34;&gt;&amp;#39;{&amp;#34;data&amp;#34;: {&amp;#34;caa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_caa.csv&amp;#34;, &amp;#34;waa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_waa.csv&amp;#34;, &amp;#34;maa_url&amp;#34;: &amp;#34;https://raw.githubusercontent.com/ichimomo/frasyr/dev/data-raw/ex2_maa.csv&amp;#34;}, &amp;#34;params&amp;#34;: {&amp;#34;m&amp;#34;: 0.5, &amp;#34;fc_year&amp;#34;: [2015, 2016, 2017], &amp;#34;tf_year&amp;#34;: [2015, 2016], &amp;#34;term_f&amp;#34;: &amp;#34;max&amp;#34;, &amp;#34;stat_tf&amp;#34;: &amp;#34;mean&amp;#34;, &amp;#34;pope&amp;#34;: true, &amp;#34;tune&amp;#34;: false, &amp;#34;p_init&amp;#34;: 0.5}}&amp;#39;&lt;/span&gt; http:&lt;span style=&#34;color:#719e07&#34;&gt;//&lt;/span&gt;localhost:&lt;span style=&#34;color:#2aa198&#34;&gt;31502&lt;/span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;/&lt;/span&gt;v0&lt;span style=&#34;color:#719e07&#34;&gt;/&lt;/span&gt;vpa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; Host localhost:&lt;span style=&#34;color:#2aa198&#34;&gt;31502&lt;/span&gt; was resolved&lt;span style=&#34;color:#719e07&#34;&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; IPv6: ::&lt;span style=&#34;color:#2aa198&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; IPv4: &lt;span style=&#34;color:#2aa198&#34;&gt;127.0&lt;/span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#2aa198&#34;&gt;0.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt;   Trying [::&lt;span style=&#34;color:#2aa198&#34;&gt;1&lt;/span&gt;]:&lt;span style=&#34;color:#2aa198&#34;&gt;31502.&lt;/span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;..&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; connect to ::&lt;span style=&#34;color:#2aa198&#34;&gt;1&lt;/span&gt; port &lt;span style=&#34;color:#2aa198&#34;&gt;31502&lt;/span&gt; from ::&lt;span style=&#34;color:#2aa198&#34;&gt;1&lt;/span&gt; port &lt;span style=&#34;color:#2aa198&#34;&gt;51462&lt;/span&gt; failed: Connection refused
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt;   Trying &lt;span style=&#34;color:#2aa198&#34;&gt;127.0&lt;/span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#2aa198&#34;&gt;0.1&lt;/span&gt;:&lt;span style=&#34;color:#2aa198&#34;&gt;31502.&lt;/span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;..&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; Connected to localhost (&lt;span style=&#34;color:#2aa198&#34;&gt;127.0&lt;/span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#2aa198&#34;&gt;0.1&lt;/span&gt;) port &lt;span style=&#34;color:#2aa198&#34;&gt;31502&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;&amp;gt;&lt;/span&gt; QUERY &lt;span style=&#34;color:#719e07&#34;&gt;/&lt;/span&gt;v0&lt;span style=&#34;color:#719e07&#34;&gt;/&lt;/span&gt;vpa HTTP&lt;span style=&#34;color:#719e07&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#2aa198&#34;&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;&amp;gt;&lt;/span&gt; Host: localhost:&lt;span style=&#34;color:#2aa198&#34;&gt;31502&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;&amp;gt;&lt;/span&gt; User&lt;span style=&#34;color:#719e07&#34;&gt;-&lt;/span&gt;Agent: curl&lt;span style=&#34;color:#719e07&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#2aa198&#34;&gt;8.7&lt;/span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#2aa198&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;&amp;gt;&lt;/span&gt; Accept: &lt;span style=&#34;color:#719e07&#34;&gt;*/*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;&amp;gt;&lt;/span&gt; Content&lt;span style=&#34;color:#719e07&#34;&gt;-&lt;/span&gt;Type: application&lt;span style=&#34;color:#719e07&#34;&gt;/&lt;/span&gt;json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;&amp;gt;&lt;/span&gt; Content&lt;span style=&#34;color:#719e07&#34;&gt;-&lt;/span&gt;Length: &lt;span style=&#34;color:#2aa198&#34;&gt;435&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;&amp;gt;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; upload completely sent off: &lt;span style=&#34;color:#2aa198&#34;&gt;435&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; Empty reply from server
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#719e07&#34;&gt;*&lt;/span&gt; Closing connection
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl: (&lt;span style=&#34;color:#2aa198&#34;&gt;52&lt;/span&gt;) Empty reply from server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;どうしようもなかったので、いったんは &lt;a href=&#34;https://schemathesis.readthedocs.io/en/stable/reference/configuration/#phasescoverageunexpected-methods:~:text=parameters%20%3D%20true-,phases.coverage.unexpected%2Dmethods,-Type%3A%20Array&#34;&gt;&lt;code&gt;phases.coverage.unexpected-methods&lt;/code&gt;&lt;/a&gt; に空配列を指定して、仕様にないメソッドを使ったケース自体の生成を止めて対応した。
plumber 側にメソッドを追加する contrubution など、時間があったら検討するかも。&lt;/p&gt;
&lt;h3 id=&#34;dns-解決に時間のかかる-url&#34;&gt;DNS 解決に時間のかかる URL&lt;/h3&gt;
&lt;p&gt;現時点の実装では、データは URL を指定して ネットワーク越しに取得するようになっている&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。
この理由から、例えば年齢別漁獲尾数をロードするために使われるパラメータ &lt;code&gt;caa_url&lt;/code&gt; は &lt;code&gt;format: uri&lt;/code&gt; になっているのだが、この情報をもとに Schemathesis は fuzzy test phase ではランダムな URL を生成してリクエストを投げてくる。
このランダムな URL の中に、まれに valid（ホストがいる）だが DNS 解決に時間がかかるものがあり（例えば &lt;code&gt;A.aG&lt;/code&gt;）、そのせいで契約テストが失敗することがあった。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ dig A.aG
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.10.6 &amp;lt;&amp;lt;&amp;gt;&amp;gt; A.aG
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; global options: +cmd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; Got answer:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: SERVFAIL, id: 30374
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; OPT PSEUDOSECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;; EDNS: version: 0, flags:; udp: 4096
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;A.aG.				IN	A
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; Query time: 3003 msec
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; SERVER: 2404:1a8:7f01:a::3#53(2404:1a8:7f01:a::3)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; WHEN: Mon Jul 13 03:16:29 JST 2026
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;;; MSG SIZE  rcvd: 33
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;テストを通すにはそのような URL の生成を抑制する方法も考えられたが、ドメインと無関係な知識（？）を持ち込むことになるのでやめた。
代わりに、DNS 解決に内部タイムアウトを設け、タイムアウトした場合には &lt;code&gt;Cannot resolve hostname&lt;/code&gt; として &lt;code&gt;404&lt;/code&gt; を返すようにした。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-R&#34; data-lang=&#34;R&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;check_dns &lt;span style=&#34;color:#719e07&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#268bd2&#34;&gt;function&lt;/span&gt;(hostname) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  result &lt;span style=&#34;color:#719e07&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#268bd2&#34;&gt;system&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#268bd2&#34;&gt;sprintf&lt;/span&gt;(&lt;span style=&#34;color:#2aa198&#34;&gt;&amp;#34;timeout 2 nslookup %s &amp;gt;/dev/null 2&amp;gt;&amp;amp;1&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#268bd2&#34;&gt;shQuote&lt;/span&gt;(hostname)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    intern &lt;span style=&#34;color:#719e07&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#cb4b16&#34;&gt;FALSE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#586e75&#34;&gt;# timeout returns 124 if timed out, nslookup returns 1 if not found&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#268bd2&#34;&gt;if&lt;/span&gt; (result &lt;span style=&#34;color:#719e07&#34;&gt;%in%&lt;/span&gt; &lt;span style=&#34;color:#268bd2&#34;&gt;c&lt;/span&gt;(&lt;span style=&#34;color:#2aa198&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#2aa198&#34;&gt;124&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#268bd2&#34;&gt;stop&lt;/span&gt;(&lt;span style=&#34;color:#268bd2&#34;&gt;vpa_error&lt;/span&gt;(&lt;span style=&#34;color:#268bd2&#34;&gt;sprintf&lt;/span&gt;(&lt;span style=&#34;color:#2aa198&#34;&gt;&amp;#34;Cannot resolve hostname: %s&amp;#34;&lt;/span&gt;, hostname), &lt;span style=&#34;color:#2aa198&#34;&gt;404&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;条件つき-nullability-のパラメータ&#34;&gt;条件つき nullability のパラメータ&lt;/h3&gt;
&lt;p&gt;frasyr の &lt;code&gt;vpa()&lt;/code&gt; の引数のうち、nullability が条件付きのものがある。
&lt;a href=&#34;https://ichimomo.github.io/frasyr/articles/vpa.html&#34;&gt;frasyr の利用法ドキュメント&lt;/a&gt;を見ると、例えば &lt;code&gt;abund&lt;/code&gt; などのチューニングに使う引数は &lt;code&gt;tune: true&lt;/code&gt; のときだけ必須となる。
このあたりを API 仕様にきちんと書かないと、fuzzy test がパターンを網羅的に生成するので &lt;code&gt;5XX&lt;/code&gt; 地獄になる。
意図しない組み合わせで呼ばれると、正しい使い方を促すエラーが frasyr からちゃんと出ているのだが、API としては仕様違反となって &lt;code&gt;500&lt;/code&gt; が返ってしまう。
直訳するならば JSON schema の &lt;a href=&#34;https://json-schema.org/understanding-json-schema/reference/conditionals&#34;&gt;&lt;code&gt;dependentRequired&lt;/code&gt;&lt;/a&gt; を使ってできそうではあるが、例えば optional なオブジェクト型の &lt;code&gt;withTune&lt;/code&gt; パラメータを定義したほうがシンプルかもしれない。
ひとまず、今回は時間がなかったので &lt;code&gt;tune&lt;/code&gt; が &lt;code&gt;false&lt;/code&gt; しか受け付けないものとして逃げた。
これはチューニング VPA をサポートする issue として個別に対応したほうがいいだろう。&lt;/p&gt;
&lt;h2 id=&#34;学んだこと&#34;&gt;学んだこと&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一つのエンドポイントが多くのパラメータを受け取ると、contract test が大規模になりがち&lt;/li&gt;
&lt;li&gt;Fuzzy test は強力。静的な生成物のよい使い方って感じ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Contract test にやや手こずったが、依存の向きを逆転させて無事に API 契約駆動で開発できるようになったので今回はここまで。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&#34;https://rindrics.com/images/2026-07-12-api-contract/contract-ssot.webp&#34;
    alt=&#34;契約の生成（左）をやめて、API を契約に準拠させられるようになった（右）&#34;&gt;&lt;figcaption&gt;
      &lt;p&gt;契約の生成（左）をやめて、API を契約に準拠させられるようになった（右）&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;参考情報&#34;&gt;参考情報&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://martinfowler.com/bliki/ContractTest.html&#34;&gt;Contract Testing | Martin Fowler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://spec.openapis.org/oas/v3.2.0&#34;&gt;OpenAPI Specification 3.2.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://scalar.com/&#34;&gt;Scalar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://schemathesis.readthedocs.io/en/stable/&#34;&gt;Schemathesis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ichimomo.github.io/frasyr/articles/vpa.html&#34;&gt;VPA関数を使った資源量推定 | ichimomo/frasyr&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.shoeisha.co.jp/book/detail/9784798179797&#34;&gt;Yehonathan Sharvit (2023) &amp;ldquo;データ指向プログラミング&amp;rdquo; 株式会社クイープ (訳), 翔泳社&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;将来的にデータ配信サービスを導入する可能性があるため&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
    </item>
    
  </channel>
</rss>
