mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 15:30:16 +03:00
chore: Новый черновик DRAFT, удалить старые docs-v
This commit is contained in:
34
examples/demo-backend/src/scripts/generate-openapi.ts
Normal file
34
examples/demo-backend/src/scripts/generate-openapi.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { createComplexApplication } from "../apps/complex/bootstrap";
|
||||
import { createSimpleApplication } from "../apps/simple/bootstrap";
|
||||
|
||||
async function generate(): Promise<void> {
|
||||
const outputDirectory = resolve(process.cwd(), "openapi");
|
||||
await mkdir(outputDirectory, { recursive: true });
|
||||
|
||||
const simple = await createSimpleApplication(false);
|
||||
await simple.app.init();
|
||||
await writeFile(
|
||||
resolve(outputDirectory, "simple.json"),
|
||||
`${JSON.stringify(simple.document, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await simple.app.close();
|
||||
|
||||
const complex = await createComplexApplication(false);
|
||||
await complex.app.init();
|
||||
await writeFile(
|
||||
resolve(outputDirectory, "complex.json"),
|
||||
`${JSON.stringify(complex.document, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await complex.app.close();
|
||||
|
||||
console.log("Generated openapi/simple.json and openapi/complex.json");
|
||||
}
|
||||
|
||||
void generate().catch((error: unknown) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
92
examples/demo-backend/src/scripts/validate-openapi.ts
Normal file
92
examples/demo-backend/src/scripts/validate-openapi.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import SwaggerParser from "@apidevtools/swagger-parser";
|
||||
import type { OpenAPIObject } from "@nestjs/swagger";
|
||||
|
||||
interface ContractExpectation {
|
||||
file: string;
|
||||
requiredSecuritySchemes: string[];
|
||||
requiredPath: string;
|
||||
forbiddenPath: string;
|
||||
}
|
||||
|
||||
async function validateContract(
|
||||
expectation: ContractExpectation,
|
||||
): Promise<void> {
|
||||
const filePath = resolve(process.cwd(), "openapi", expectation.file);
|
||||
await SwaggerParser.validate(filePath);
|
||||
const document = JSON.parse(
|
||||
await readFile(filePath, "utf8"),
|
||||
) as OpenAPIObject;
|
||||
const operationIds = new Set<string>();
|
||||
let operationCount = 0;
|
||||
|
||||
for (const pathItem of Object.values(document.paths)) {
|
||||
if (!pathItem) continue;
|
||||
for (const method of [
|
||||
"get",
|
||||
"post",
|
||||
"put",
|
||||
"patch",
|
||||
"delete",
|
||||
"options",
|
||||
"head",
|
||||
] as const) {
|
||||
const operation = pathItem[method] as
|
||||
{ operationId?: string } | undefined;
|
||||
if (!operation) continue;
|
||||
operationCount += 1;
|
||||
if (!operation.operationId)
|
||||
throw new Error(
|
||||
`${expectation.file}: ${method.toUpperCase()} operation has no operationId.`,
|
||||
);
|
||||
if (operationIds.has(operation.operationId))
|
||||
throw new Error(
|
||||
`${expectation.file}: duplicate operationId ${operation.operationId}.`,
|
||||
);
|
||||
operationIds.add(operation.operationId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!document.paths[expectation.requiredPath])
|
||||
throw new Error(
|
||||
`${expectation.file}: required path ${expectation.requiredPath} is missing.`,
|
||||
);
|
||||
if (document.paths[expectation.forbiddenPath])
|
||||
throw new Error(
|
||||
`${expectation.file}: path ${expectation.forbiddenPath} leaked from the other application.`,
|
||||
);
|
||||
for (const scheme of expectation.requiredSecuritySchemes) {
|
||||
if (!document.components?.securitySchemes?.[scheme])
|
||||
throw new Error(
|
||||
`${expectation.file}: security scheme ${scheme} is missing.`,
|
||||
);
|
||||
}
|
||||
if (operationCount < 10)
|
||||
throw new Error(
|
||||
`${expectation.file}: suspiciously small contract (${operationCount} operations).`,
|
||||
);
|
||||
console.log(
|
||||
`Validated ${expectation.file}: ${operationCount} operations, ${operationIds.size} unique operation IDs.`,
|
||||
);
|
||||
}
|
||||
|
||||
async function validate(): Promise<void> {
|
||||
await validateContract({
|
||||
file: "simple.json",
|
||||
requiredSecuritySchemes: ["jwt"],
|
||||
requiredPath: "/api/v1/products",
|
||||
forbiddenPath: "/api/v1/organizations",
|
||||
});
|
||||
await validateContract({
|
||||
file: "complex.json",
|
||||
requiredSecuritySchemes: ["cookieSession", "csrf"],
|
||||
requiredPath: "/api/v1/organizations",
|
||||
forbiddenPath: "/api/v1/auth/refresh-token",
|
||||
});
|
||||
}
|
||||
|
||||
void validate().catch((error: unknown) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user