sgcOpenAPI 2026.9.0: パーサーの全面刷新

· リリース
sgcOpenAPI 2026.9.0、Delphi 向けの OpenAPI パーサーおよびコードジェネレーター

sgcOpenAPI 2026.9.0 は、この製品にとって過去最大のリリースです。以前のバージョンは、ほとんどのチュートリアルが示す形の仕様書なら扱えましたが、それ以外ではひそかに動作が劣化していました。今回はパーサーを機能ごとに、OpenAPI 3.0、3.1、3.2 の仕様と、実際に公開されているドキュメントに照らして見直しました。その結果が、9 個の新機能、26 件のバグ修正、そして意図的な 5 つの破壊的変更です。

手短に言えば、これまでコンパイルできないコードを生成していた仕様書、さらに悪いことにコンパイルは通るのに間違った URL を呼び出すコードを生成していた仕様書について、生成されるクライアントが正しくなりました。

パーサーができなかったことを教えてくれます

以前のパーサーが問題を報告する方法は例外を投げることだけで、それ以外はすべて黙って処理を続けるだけでした。生成できなかったオペレーションは単に出力に含まれず、存在しないメソッドを探しに行ったときに初めて気づくことになりました。

すべてのドキュメントが Warnings リストを返すようになりました。openapiinfo メンバーの欠落、JSON 型が誤っているメンバー、生成できなかったオペレーション、解決できなかったパスアイテム参照、読み取られてはいるがまだ反映されていない JSON Schema キーワードが、すべてそこに記録されます。リストは読み込みのたびにクリアされるので、得られる内容は今解析したドキュメントのものです。

uses
  sgcOpenAPI_Classes, sgcOpenAPI_Parser_Client_Pascal;

var
  oParser: TsgcOpenAPI_Parser_Client_Pascal;
  i: Integer;
begin
  oParser := TsgcOpenAPI_Parser_Client_Pascal.Create;
  Try
    oParser.OpenAPIClassName := 'TPetStoreClient';
    oParser.OpenAPINamespace := 'PetStore';
    oParser.OutputFileName := 'PetStoreClient.pas';

    oParser.ReadFromFile('petstore.json');

    for i := 0 to oParser.Warnings.Count - 1 do
      Memo1.Lines.Add('warning: ' + oParser.Warnings[i]);

    oParser.SaveToFile('PetStoreClient.pas');
  Finally
    oParser.Free;
  End;
end;

保存する前に OutputFileName を設定してください。Pascal のユニットは、宣言された名前がファイルのベース名と一致するときにしかコンパイルできませんが、ジェネレーターは入力ドキュメントの名前をユニット名に使っていました。そのため petstore.json から MyClient.pas を生成すると petstore という名前のユニットができてしまい、コンパイルできませんでした。今は出力側の名前が優先されます。

どのバージョンを読んでいるかを認識します

OpenAPI 3.0 と 3.1 は、同じ名前を持つキーワードの意味が食い違っていますが、以前のパーサーはすべてのドキュメントを 3.0 として扱っていました。最も分かりやすい例が exclusiveMinimum で、3.0 では minimum に対する真偽値の修飾子、3.1 ではそれ自体が数値です。一方を他方として読むと、境界値が誤ります。

バージョンは方言として解析され、DialectDialectMajorDialectMinor として公開されます。意味が異なるキーワードは、それぞれのバージョンが求めるとおりに読まれます。

oParser.ReadFromFile('api.yaml');

case oParser.Dialect of
  oapiDialect30: ShowMessage('OpenAPI 3.0');
  oapiDialect31: ShowMessage('OpenAPI 3.1');
  oapiDialect32: ShowMessage('OpenAPI 3.2');
end;

さらに 3.1 では、webhooksjsonSchemaDialectcomponents.pathItems、ライセンスの identifiermutualTLS セキュリティスキーム、["string","null"] のように配列として宣言された型、そして単なる真偽値として宣言されたスキーマが加わりました。そのすべてに対応しています。コードジェネレーターがまだ処理しない JSON Schema 2020-12 のキーワードはモデルに読み込まれ、Warnings を通じて報告されるので、足りない部分が見えない形ではなく見える形になります。

3.2 からは、query オペレーションと additionalOperations マップに対応しています。どちらかを宣言しているパスは対応するメソッドを生成し、X-HTTP-Method-Override ヘッダーを付けた POST として送信されます。

パスレベルのパラメータ

これは、ほとんどのユーザーが効果を実感する修正です。仕様では、配下のすべてのオペレーションで繰り返す代わりに、パスアイテムでパラメータを一度だけ宣言できます。これは仕様が推奨するスタイルであり、公開されているドキュメントの多くが使っているスタイルでもあります。以前のパーサーは、それらのパラメータを読み取ったうえで破棄していました。

生成されるメソッドは引数をまったく取らず、リクエストは URL にプレースホルダーを残したまま、文字どおり /pets/{petId} のかたちで送信されていました。最初の呼び出しが 404 を返すまでは、正しく動くクライアントに見えていました。

正しく合成されるスキーマ

合成は以前のパーサーで最も弱い部分で、どの分岐もそれぞれ違う形で間違っていました。allOf は複数あるベーススキーマのうち最後のものだけを残し、ほかのメンバーを捨てていました。oneOf はすべての分岐を 1 つのクラスにまとめてしまい、重複したフィールドが生じていました。anyOf はまったく処理されず、文字列として解決されていました。propertiesadditionalProperties の両方を宣言したスキーマは、プロパティをすべて失っていました。

4 つとも、ドキュメントが記述しているとおりに生成されるようになりました。インラインのオブジェクトスキーマも、文字列に劣化させるのではなく専用のクラスを持ちます。items は完全なスキーマとして読まれるので、インラインオブジェクトの配列、enum の配列、入れ子になった配列のいずれもが正しい型を生成します。

サーバーが期待する値を保持する enum

生成される enum のテーブルには、実際に送信される値ではなく、整形された Pascal の識別子が入っていました。そのため allow-allallowall として、json-filejsonfile として送信されていました。そうした enum から組み立てられたリクエストは、すべて拒否されていました。

テーブルは実際の値を保持するようになり、仕様上の宣言順が維持され、整数の enum にもテーブルが生成されます。さらに Unknown メンバーが追加で生成されるので、サーバーが後から追加した値が、黙ってリストの先頭のメンバーとしてデコードされることはありません。

プロパティ名も、反対側から同じ扱いを受けます。propertyclassstringfunction のような Delphi の予約語と同じ名前のスキーマプロパティや、Namename のように大文字と小文字しか違わない 2 つのプロパティは、コンパイルできないユニットを生んでいました。今はプロパティの名前が変更され、送信時の名前は JSONName 属性で保持されるので、シリアライズはドキュメントと一致したままです。

一度しか宣言しないものも含めたレスポンス

default レスポンスと、2XX4XX5XX という範囲指定のレスポンスは、黙って破棄されていました。エラーを default だけで宣言する API はよくありますが、そうした API からは型付きのエラーがまったくないクライアントが生成されていました。今はこれらも読み取られます。成功レスポンスが複数宣言されている場合は最も小さいものが使われ、オペレーションが複数のメディアタイプを提供している場合は application/json が優先されます。

送信されるパラメータ

生成されるクライアントは、cookie パラメータと、OpenAPI のパラメータシリアライズ規則一式に対応しました。matrixlabelsimpleformspaceDelimitedpipeDelimiteddeepObject のそれぞれで explodeallowReserved が使えます。新しい AddArrayAddObject メソッドを使えば、必要なときに構造化された値を手作業で組み立てられます。

// query parameters built with the serialization style the document declares
oRequest.AddArray('tags', ['dog', 'cat'], True);   // explode
oRequest.AddObject('filter', ['color', 'red', 'size', 'M']);

外部参照

複数のファイルに分割された仕様書は、ほとんど動きませんでした。./common.yaml#/components/schemas/Error のように JSON Pointer のフラグメントを含む参照は解決できませんでした。互いに参照し合う 2 つのファイルは、パーサーをクラッシュさせました。サブドキュメント内の相対参照は、そのファイル自身ではなくルートのドキュメントを基準に解決されていました。ベース名が同じ 2 つの外部ファイルは互いを上書きし、メインドキュメントに属するスキーマを置き換えることさえありました。そして参照の連鎖は、ちょうど 1 段階しかたどられませんでした。

これらはすべて修正され、1 点だけ意図的に厳しくしました。以前は外部参照でマシン上の任意のファイルを読むことができ、../../../credentials.json も例外ではなく、その内容を生成されるユニットにコピーできてしまいました。外部参照は今、メインドキュメントのディレクトリ内に限定されます。構成上どうしてもその外に出る必要がある場合は、明示的に制限を解除します。

uses
  sgcOpenAPI_Bundle;

begin
  // off by default: references may not leave the folder of the main document
  sgcOpenAPIAllowRefsOutsideRoot := True;
end;

完全な UTF-8 ではないファイル

RFC 8259 は JSON ドキュメントが UTF-8 であると定めていますが、公開されている仕様書には UTF-8 でないものが数多くあります。バイトオーダーマークを持つファイルは、ASCII の範囲外の文字を 1 つでも含んだ時点で UTF-8 エラーとして拒否され、中国語や日本語のテキストは黙って疑問符に置き換えられていました。

有効な UTF-8 ではないドキュメントは、失敗する代わりに、警告を記録したうえで Windows-1252 として読まれます。バイトオーダーマーク付きの UTF-16 ファイルも正しく読み取られます。生成されるファイルは明示的なエンコーディングで書き出され、対象のエンコーディングで表現できない文字は、黙って疑問符になるのではなく報告されます。

ビルドスクリプトに組み込めるコマンドライン

コマンドラインは終了コードを設定するようになりました。成功時は 0、失敗の種類に応じて 1 から 7 を返すので、ビルドの工程で生成が成功したかどうかを判断できます。エラーメッセージは常に標準エラー出力へ送られ、-l スイッチは進捗ログ専用になりました。

sgcOpenAPI -i petstore.json -o PetStoreClient.pas -c TPetStoreClient
if errorlevel 1 (
  echo OpenAPI generation failed with exit code %errorlevel%
  exit /b %errorlevel%
)

これに合わせて、コマンドラインのバグを 3 つ修正しました。ドキュメントに記載されている -output スイッチは、カレントディレクトリの utput というファイルにユニットを書き出していましたが、メッセージが抑制されていたため実行は成功したように見えていました。コンソールが接続されていない状態でツールを実行すると何も起こらず、これはスケジュールされたタスクやビルドエージェントではまさに当てはまる状況で、既存の出力リダイレクトも破棄されていました。そして -h は、アクティベートされていないマシンでは使い方のテキストではなくライセンスエラーを表示し、-m-a に無効な値を渡しても黙って受け入れられ、未知のスイッチは無視されていました。

本リリースの新機能として、-r (または -remote) は YAML や Swagger 2.0 のドキュメントを converter.swagger.io の公開コンバーターで変換します。ドキュメントを第三者に送信することになるため既定では無効で、承知したうえで有効にするものです。

Swagger 2.0 の変換自体にも、挙げておくべき壊れ方が 2 つありました。すべての数値が文字列になってしまい、数値のデフォルト値はコンパイルできないユニットを生み、変換後のドキュメントは有効な OpenAPI 3.0 ではありませんでした。また、Swagger 2.0 では単なる文字列である discriminator が、不正な型キャストで解析全体を中断させていました。

破壊的変更

次の 5 つの変更は、単にアップグレードするだけでなく、お客様の判断を必要とします。

生成されるクライアントは、サーバー証明書を検証するようになりました。以前は検証しておらず、中間者が提示したものを含め、あらゆる証明書を受け入れていました。自己署名やテスト用のエンドポイントに接続するには、意図的に検証を無効にしてください。

oClient.TLSOptions.VerifyCertificate := False;   // test endpoints only
// certificates are trusted through the OpenSSL default paths, so a machine
// with no certificate store configured needs an explicit root
oClient.TLSOptions.RootCertFile := 'cacert.pem';

リクエストボディは UTF-8 です。RFC 8259 が求めるとおりです。クラスは空文字列を省略せず、"field": "" としてシリアライズするようになりました。null 値は別途制御します。

oClient.JSONIgnoreEmptyStrings := True;  // previous output
oClient.JSONIgnoreNullValues := True;    // default

レスポンスは、お客様が渡した ResponseStream を解放しなくなりました。以前の動作にするには OwnsResponseStream を True に設定してください。クライアント自身の OnResponseOnErrorOnCancel ハンドラーの中からクライアントを解放すると、ハングするのではなく明確なエラーが発生するようになりました。

コマンドラインのスイッチの値は -name value または -name:value の形式で書く必要があります。区切りなく連結する -x"GET /pets" のような形式は、受け付けなくなりました。この形式こそが、-x-xml のような x で始まる別のスイッチに一致させていた原因でもあります。

配列として宣言されたパラメータは、配列として生成されます。以前は文字列として生成されていたため、該当するオペレーションでは、生成されるメソッドのシグネチャが変わります。

そのほかのすべて

残りの修正は、実際に問題が起きたときにしか気づかない類のものです。"properties": [] のように JSON 型が想定外のメンバーは、スキップされる代わりに、不正な型キャストで解析を中断させていました。デフォルト値のない integer 型のスキーマには 0 のデフォルト値が与えられ、値が 1 つだけの enum は定数として扱われて、生成されるメソッドからパラメータそのものが消えていました。同じドキュメントを 2 回読み込むと、パス、タグ、サーバー、スキーマがすべて重複しました。パスの中に置かれた x-tagGroups のような仕様拡張は、パスであるかのように読まれていました。enumrequiredtags はカンマ区切りのテキストヘルパーで解析されていたため、カンマや JSON のエスケープを含む値は分割されたり壊れたりしていました。複数のスキームを列挙したセキュリティ要件は最初の 1 つだけを残し、そのすべてを満たすという要件を失っていました。info.contactinfo.license は、決して真にならない条件判定のせいで、まったく読まれていませんでした。複数の変数を含むサーバー URL は誤った値を置換し、リストのインデックスエラーを発生させることがありました。仕様書のバンドル処理は、バックアップもメッセージもなく入力ファイルを上書きし、ドキュメントからタイポグラフィ用のアポストロフィをすべて削除していました。そして C:\My Specs\ のように空白を含むパスに置かれた仕様書は、外部参照を解決できませんでした。

入手方法

sgcOpenAPI 2026.9.0 は、完全なソースコードと 1 年間のアップデート付きで、今すぐご利用いただけます。Delphi 7 から Delphi 13 Florence まで、および対応する C++ Builder のバージョンをサポートしています。

製品ページ · 体験版をダウンロード · 変更履歴

ご質問やご意見がありましたら、お問い合わせください。コードを書いた本人から返信します。