openAPIの$refを展開できるようにした
前提・経緯
- 個人でwebサービスを作るときに共通の型をフロントエンドとバックエンドで使いたかったので
"openapi-typescript": "6.7.2"
を使用 - フォルダで区切ってschemaを管理したかったので$refを使ってる
- 色々作ってたら何かしらのスキーマを展開した上でプロパティを追加したくなった
例
事前に定義されているデータ
// user
{
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" }
}
}
// post
{
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" }
}
}
今回定義したいschema
user
の情報に追加でlatestPost
というpost
型のデータを返したい
// userInfoAPI
{
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"latestPost": {
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" }
}
}
}
}
愚直に$ref
を使った場合
schema
{
"type": "object",
"properties": {
"$ref": "../schemas/user/model.json",
"latestPost": { "$ref": "../schemas/post/model.json" }
}
}
output.ts
"schemas/user/userInfo.json": {
$ref?: external["schemas/user/model.json"];
latestPost: external["schemas/post/model.json"];
};
展開した方法
apidevtools/json-schema-ref-parser
を用いて中間で$ref
を展開したschema
を生成するようにした
具体的には$ref
が見つかったら解決して特定のkey
のオブジェクトを上書きしている
const replaceRefs = async (schema: JSONSchema) => {
for (const [key, value] of Object.entries(schema)) {
try {
const resolverRef = await $RefParser.dereference(value["$ref"], options);
Object.assign(schema, {
[key]: {
type: "object",
properties: { ...resolverRef },
required: requiredDataStore,
},
});
requiredDataStore = [];
} catch (error) {
throw Error(error);
}
}
};
option
には読み込む時の処理を入れることができ、arrayの中に$ref
が入った時の処理を追加している
(resolveArrayItemRefはこちらが定義した独自の関数)
const options: ParserOptions = {
resolve: {
file: {
order: 1,
read: async (fileInfo: FileInfo) => {
const readResult = await fs.readFile(fileInfo.url, "utf-8");
const parsedResult = JSON.parse(readResult);
requiredDataStore = Array.from(
new Set([...requiredDataStore, ...parsedResult["required"]]),
);
// $RefParser.dereferenceはおそらく"$ref"の時は自動で再帰的に解決してくれるが{ type:array, items:$ref }の時は見てくれなさそうなのでこちら側で対応する必要がある
// なのでここでitemsの中に$refがあったら再帰的に処理するためにresolveArrayItemRefを呼び出す。
await resolveArrayItemRef(parsedResult["properties"], fileInfo.url);
return parsedResult["properties"];
},
},
},
};
なんか問題になりそうなところ
自分がopenAPIを今使ってる限りでは簡単なことを表現するためにしか使ってないので問題になっていないが、oneOf, anyOf, allOf, not等を使う時にはparse
が失敗するとかはありそう
感想
自分がどうにかならないかなと思っていた問題を解決できたのは嬉しかったけど、そもそもこういうケースになるのが自分の設計部分の間違いとか、openAPI
の仕組みでにもっと良い解決方法があるとかは全然ある気がずっとしている…
あと、実装したのと記事を書くのにしばらく時間が空いていたのでかなりうる覚えで書いているところがある