Govern wire schemas from TypeSpec

This commit is contained in:
ms2k 2026-07-19 02:00:47 +09:00
commit 2020dd4ed3
6 changed files with 10189 additions and 1571 deletions

2
contracts/typespec/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
generated/
node_modules/

View file

@ -0,0 +1,107 @@
import { spawnSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { parse, stringify } from "yaml";
const root = dirname(fileURLToPath(import.meta.url));
const repository = resolve(root, "../..");
const check = process.argv.includes("--check");
const compiler = resolve(root, "node_modules/.bin/tsp");
const result = spawnSync(compiler, ["compile", root], {
cwd: root,
encoding: "utf8",
});
if (result.status !== 0) {
process.stderr.write(result.stdout);
process.stderr.write(result.stderr);
process.exit(result.status ?? 1);
}
const generatedRust = resolve(root, "generated/rust/remora_wire.rs");
const rustfmt = spawnSync("rustfmt", ["--edition", "2024", generatedRust], {
cwd: root,
encoding: "utf8",
});
if (rustfmt.status !== 0) {
process.stderr.write(rustfmt.stdout);
process.stderr.write(rustfmt.stderr);
process.exit(rustfmt.status ?? 1);
}
const openapiDocument = parse(
readFileSync(resolve(root, "generated/openapi/openapi.yaml"), "utf8"),
);
openapiDocument.security = [{ oidc: [] }];
openapiDocument.components ??= {};
openapiDocument.components.securitySchemes = {
oidc: {
type: "openIdConnect",
openIdConnectUrl: "https://identity.invalid/.well-known/openid-configuration",
},
serviceAuth: { type: "mutualTLS" },
eventTicket: { type: "apiKey", in: "query", name: "ticket" },
};
for (const [path, item] of Object.entries(openapiDocument.paths)) {
for (const operation of Object.values(item)) {
if (typeof operation !== "object" || operation === null || !("responses" in operation)) {
continue;
}
if (path.startsWith("/internal/")) {
operation.security = [{ serviceAuth: [] }];
} else if (path === "/v1/events") {
operation.security = [{ oidc: [] }, { eventTicket: [] }];
}
}
}
const openapi = stringify(openapiDocument, { lineWidth: 0 });
const bundle = JSON.parse(
readFileSync(resolve(root, "generated/json-schema/remora-wire.json"), "utf8"),
);
rewriteBundleReferences(bundle);
for (const schema of Object.values(bundle.$defs)) {
delete schema.$schema;
delete schema.$id;
}
const events = {
...bundle,
$id: "https://remora.invalid/api/events.schema.json",
title: "Remora WebSocket frame",
$ref: "#/$defs/EventFrame",
};
const outputs = [
[resolve(repository, "docs/api/openapi.yaml"), openapi],
[resolve(repository, "docs/api/events.schema.json"), `${JSON.stringify(events, null, 2)}\n`],
[
resolve(repository, "crates/remora-contracts/src/generated_wire.rs"),
readFileSync(generatedRust, "utf8"),
],
];
let stale = false;
for (const [path, content] of outputs) {
if (check) {
if (readFileSync(path, "utf8") !== content) {
console.error(`${path} is not generated from contracts/typespec/main.tsp`);
stale = true;
}
} else {
writeFileSync(path, content);
}
}
if (stale) {
process.exit(1);
}
function rewriteBundleReferences(value) {
if (Array.isArray(value)) {
value.forEach(rewriteBundleReferences);
return;
}
if (value === null || typeof value !== "object") {
return;
}
if (typeof value.$ref === "string" && /^[A-Za-z][A-Za-z0-9]*\.json$/.test(value.$ref)) {
value.$ref = `#/$defs/${value.$ref.slice(0, -5)}`;
}
Object.values(value).forEach(rewriteBundleReferences);
}

1382
contracts/typespec/main.tsp Normal file

File diff suppressed because it is too large Load diff

1265
contracts/typespec/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
{
"name": "remora-wire-contract",
"private": true,
"type": "module",
"scripts": {
"generate": "node generate.mjs",
"check": "node generate.mjs --check",
"compile": "tsp compile ."
},
"devDependencies": {
"@remora/typespec-rust": "file:../typespec-rust-emitter",
"@typespec/compiler": "1.14.0",
"@typespec/http": "1.14.0",
"@typespec/json-schema": "1.14.0",
"@typespec/openapi": "1.14.0",
"@typespec/openapi3": "1.14.0",
"yaml": "2.9.0"
}
}

View file

@ -0,0 +1,22 @@
emit:
- "@remora/typespec-rust"
- "@typespec/openapi3"
- "@typespec/json-schema"
options:
"@remora/typespec-rust":
emitter-output-dir: "{output-dir}/rust"
"@typespec/openapi3":
emitter-output-dir: "{output-dir}/openapi"
file-type: yaml
openapi-versions:
- "3.2.0"
safeint-strategy: double-int
"@typespec/json-schema":
emitter-output-dir: "{output-dir}/json-schema"
file-type: json
bundleId: remora-wire.json
emitAllModels: true
emitAllRefs: true
int64-strategy: number
seal-object-schemas: true
output-dir: "{project-root}/generated"