Govern wire schemas from TypeSpec
This commit is contained in:
parent
fa81cb7f21
commit
2020dd4ed3
6 changed files with 10189 additions and 1571 deletions
2
contracts/typespec/.gitignore
vendored
Normal file
2
contracts/typespec/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
generated/
|
||||
node_modules/
|
||||
107
contracts/typespec/generate.mjs
Normal file
107
contracts/typespec/generate.mjs
Normal 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
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
1265
contracts/typespec/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
contracts/typespec/package.json
Normal file
19
contracts/typespec/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
22
contracts/typespec/tspconfig.yaml
Normal file
22
contracts/typespec/tspconfig.yaml
Normal 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"
|
||||
Loading…
Reference in a new issue