Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@quasar/extras": "1.16.17",
"@sevenc-nanashi/utaformatix-ts": "npm:@jsr/[email protected]",
"@std/path": "npm:@jsr/[email protected]",
"ajv": "8.17.1",
"async-lock": "1.4.1",
"dayjs": "1.11.13",
"electron-log": "5.3.1",
Expand Down
36 changes: 35 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 104 additions & 6 deletions src/store/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { cloneWithUnwrapProxy } from "@/helpers/cloneWithUnwrapProxy";
import openapi from "../../openapi.json";

Check failure on line 2 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

`../../openapi.json` import should occur before import of `@/helpers/cloneWithUnwrapProxy`
import { ProxyStoreState, ProxyStoreTypes, EditorAudioQuery } from "./type";

Check failure on line 3 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

`./type` import should occur before import of `@/helpers/cloneWithUnwrapProxy`
import { createPartialStore } from "./vuex";

Check failure on line 4 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

`./vuex` import should occur before import of `@/helpers/cloneWithUnwrapProxy`
import { createEngineUrl } from "@/domain/url";
Expand All @@ -7,11 +9,107 @@
OpenAPIEngineAndMockConnectorFactory,
OpenAPIEngineConnectorFactory,
} from "@/infrastructures/EngineConnector";
import { AudioQuery } from "@/openapi";
import { AudioQuery, DefaultApiInterface } from "@/openapi";
import { EngineInfo } from "@/type/preload";
import Ajv, { ValidateFunction } from "ajv";

Check failure on line 14 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

`ajv` import should occur before import of `@/helpers/cloneWithUnwrapProxy`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ちなみにzodでできたりしないですかね?
(依存を減らしたい)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zodはjson schemaを扱えませんね。

方法としてはjson-schema-to-zodを走らせるツールをtools下に生やすのが現実的かも?(それを作るなら一緒にopenapi generatorも走らせたい気持ちがある)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あーそうか。。

それを作るなら一緒にopenapi generatorも走らせたい

これってpackage.jsonでインストールしてるgeneratorのことですかね?それとも自作とか?
(自作だと流石に避けたい)

うーーーーん メンテが結構大変かもな気がしてきました。
ちょっと別のとこでコメントします!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これってpackage.jsonでインストールしてるgeneratorのことですかね?それとも自作とか?

package.jsonのやつですね。イメージとしてはpnpm run tools:update-openapi http://localhost:50021をするとopenapi-generator-cliとかが走るイメージ。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あーなるほどです!!
それは結構嬉しそう感。


export const proxyStoreState: ProxyStoreState = {};

const validateOpenApiResponse = createValidateOpenApiResponse();

function toCamelCase(str: string) {
return str.replace(/_./g, (s) => s.charAt(1).toUpperCase());
}

/** OpenAPIのレスポンスを検証する */
function createValidateOpenApiResponse() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ちょっとこの量のコードをここに加えるのはメンテ性下がる気がするので(今更ですが。。。)、このファイルをsrc/store/proxy/index.tsに移して、この関数をsrc/store/proxy/以下のファイルに移動するのはどうでしょう?

const ajv = new Ajv().addSchema({
$id: "openapi.json",
definitions: patchOpenApiJson(openapi).components.schemas,
});
const validatorCache = new Map<string, ValidateFunction>();

for (const path of Object.values(openapi.paths)) {
for (const method of Object.values(path)) {
const schema =

Check failure on line 34 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe assignment of an `any` value
method.responses["200"]?.content?.["application/json"]?.schema;

Check failure on line 35 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .responses on an `any` value
if (schema == null) {
continue;
}
validatorCache.set(
toCamelCase(method.operationId),

Check failure on line 40 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .operationId on an `any` value

Check failure on line 40 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe argument of type `any` assigned to a parameter of type `string`
ajv.compile(patchOpenApiJson(schema)),
);
}
}

return <K extends keyof DefaultApiInterface>(
key: K,
response: ReturnType<DefaultApiInterface[K]>,
): ReturnType<DefaultApiInterface[K]> => {
return response.then((res) => {
const maybeValidator = validatorCache.get(key);
if (maybeValidator == null) {
return res;
}

if (!maybeValidator(res)) {
throw new Error(
`Response validation error in ${key}: ${ajv.errorsText(maybeValidator.errors)}`,
);
}

return res;
}) as ReturnType<DefaultApiInterface[K]>;
};
}

/**
* OpenAPIのスキーマを修正する。
*
* 具体的には以下の変更を行う:
* - `$ref`の参照先を`#/components/schemas/`から`openapi.json#/definitions/`に変更する
* - オブジェクトのプロパティ名をキャメルケースに変換する
*/
function patchOpenApiJson<T extends Record<string, unknown>>(schema: T): T {
return inner(cloneWithUnwrapProxy(schema)) as T;

function inner(schema: Record<string, unknown>): Record<string, unknown> {
if (schema["$ref"] != null) {
const ref = schema["$ref"];
if (typeof ref === "string") {
schema["$ref"] = ref.replace(
"#/components/schemas/",
"openapi.json#/definitions/",
);
}
}

if (
Copy link
Member

@Hiroshiba Hiroshiba Mar 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こーーーーれちょっとメンテだいぶしんどい気がしますね・・・・・・・・・
openapiの何かが変わったときにこのあたりでエラー出ると、触れる人がかなり少ない気が。。。。。

補助ツールとかではなくエディタで、かつエンジン→エディタのメインストリームなのでもう少し硬いコードを書きたい気もします。
壊れたときに @sevenc-nanashi さんがかなり率先的に助けてくれる、とかならギリ行けるかも・・・・・・?

実は本当に必要なのはsupported featureのとこだけのバリデータなのでは、という気もしてきました。。。

ちょっと結論出てないのですが、とりあえず相談まで 🙇

schema["type"] === "object" &&
typeof schema["properties"] === "object" &&
schema["properties"] != null &&
Array.isArray(schema["required"])
) {
schema["properties"] = Object.fromEntries(
Object.entries(schema["properties"]).map(([key, value]) => [
toCamelCase(key),
inner(value as Record<string, unknown>),
]),
);

schema["required"] = schema["required"].map((key) => toCamelCase(key));

Check failure on line 101 in src/store/proxy.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe argument of type `any` assigned to a parameter of type `string`
}

for (const key in schema) {
if (typeof schema[key] === "object" && schema[key] != null) {
inner(schema[key] as Record<string, unknown>);
}
}
return schema;
}
}

const proxyStoreCreator = (_engineFactory: IEngineConnectorFactory) => {
const proxyStore = createPartialStore<ProxyStoreTypes>({
INSTANTIATE_ENGINE_CONNECTOR: {
Expand All @@ -35,11 +133,11 @@
);
return Promise.resolve({
invoke: (v) => (arg) =>
// FIXME: anyを使わないようにする
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return
instance[v](arg) as any,
validateOpenApiResponse(
v,
// @ts-expect-error 動いているので無視
instance[v](arg),
),
});
},
},
Expand Down
Loading