Skip to content

Commit 18cc26f

Browse files
committed
feat(code-gen): always expose the Compas structure in OpenAPI format
This allows interop with a new OpenAPI based code-generator
1 parent b395789 commit 18cc26f

File tree

4 files changed

+104
-3
lines changed

4 files changed

+104
-3
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { mainTestFn, test } from "@compas/cli";
2+
import { closeTestApp, createTestAppAndClient } from "@compas/server";
3+
import axios from "axios";
4+
import { axiosInterceptErrorAndWrapWithAppError } from "./generated/application/common/api-client.js";
5+
import { apiCompasStructure } from "./generated/application/compas/apiClient.js";
6+
import { app } from "./services/core.js";
7+
8+
mainTestFn(import.meta);
9+
10+
test("structure/controller", async (t) => {
11+
// Run the Koa instance on a random port and create an Axios instance that uses that as
12+
// the baseUrl.
13+
const axiosInstance = axios.create();
14+
await createTestAppAndClient(app, axiosInstance);
15+
axiosInterceptErrorAndWrapWithAppError(axiosInstance);
16+
17+
t.test("compas structure", async (t) => {
18+
const results = await Promise.all([
19+
apiCompasStructure(axiosInstance, {}),
20+
apiCompasStructure(axiosInstance, { format: "compas" }),
21+
]);
22+
23+
t.deepEqual(results[0], results[1]);
24+
});
25+
26+
t.test("openapi structure", async (t) => {
27+
const results = await Promise.all([
28+
apiCompasStructure(axiosInstance, {}),
29+
apiCompasStructure(axiosInstance, { format: "openapi" }),
30+
]);
31+
32+
t.notEqual(JSON.stringify(results[0]), JSON.stringify(results[1]));
33+
});
34+
35+
t.test("teardown", async (t) => {
36+
// Since subtests run in the order they are registered, we can always do some
37+
// teardown in the last subtest.
38+
39+
await closeTestApp(app);
40+
41+
t.pass();
42+
});
43+
});

packages/code-gen/src/open-api/generator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function openApiGenerate(generateContext) {
3131
/**
3232
* @param {import("../generate.js").GenerateContext} generateContext
3333
*/
34-
function openApiBuildFile(generateContext) {
34+
export function openApiBuildFile(generateContext) {
3535
const openApiSpec = merge(
3636
{
3737
openapi: "3.0.3",

packages/code-gen/src/processors/route-structure.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import { AnyType, RouteBuilder } from "../builders/index.js";
1+
import {
2+
AnyType,
3+
ObjectType,
4+
ReferenceType,
5+
RouteBuilder,
6+
StringType,
7+
} from "../builders/index.js";
28
import { Generator } from "../generator.js";
9+
import { openApiBuildFile } from "../open-api/generator.js";
310
import { structureRoutes } from "./routes.js";
411
import { structureAddType, structureNamedTypes } from "./structure.js";
512

@@ -9,6 +16,12 @@ import { structureAddType, structureNamedTypes } from "./structure.js";
916
* @type {WeakMap<object, string>}
1017
*/
1118
const routeStructureCache = new WeakMap();
19+
/**
20+
* Cache the openapi structure string based on the current generate context.
21+
*
22+
* @type {WeakMap<object, string>}
23+
*/
24+
const openApiStructureCache = new WeakMap();
1225

1326
/**
1427
* Extract the route structure from generate context
@@ -55,10 +68,38 @@ export function routeStructureCreate(generateContext) {
5568
(v) => `\\${v}`,
5669
),
5770
);
71+
openApiStructureCache.set(
72+
generateContext,
73+
JSON.stringify(
74+
openApiBuildFile({
75+
structure: routesOnlyGenerator.internalStructure,
76+
options: {
77+
generators: {
78+
openApi: {
79+
openApiExtensions: {},
80+
openApiRouteExtensions: {},
81+
},
82+
},
83+
},
84+
}),
85+
).replace(/([`\\])/gm, (v) => `\\${v}`),
86+
);
5887

88+
structureAddType(
89+
generateContext.structure,
90+
new ObjectType("compas", "structureQuery")
91+
.keys({
92+
format: new StringType().oneOf("compas", "openapi").default(`"compas"`),
93+
})
94+
.build(),
95+
{
96+
skipReferenceExtraction: false,
97+
},
98+
);
5999
structureAddType(
60100
generateContext.structure,
61101
new RouteBuilder("GET", "compas", "structure", "_compas/structure.json")
102+
.query(new ReferenceType("compas", "structureQuery"))
62103
.response(new AnyType())
63104
.tags("_compas")
64105
.docs("Return the full available API structure")
@@ -78,3 +119,13 @@ export function routeStructureCreate(generateContext) {
78119
export function routeStructureGet(generateContext) {
79120
return routeStructureCache.get(generateContext) ?? "";
80121
}
122+
123+
/**
124+
* Get the saved route structure in OpenApi format
125+
*
126+
* @param {import("../generate.js").GenerateContext} generateContext
127+
* @returns {string}
128+
*/
129+
export function routeStructureGetOpenApi(generateContext) {
130+
return openApiStructureCache.get(generateContext) ?? "";
131+
}

packages/code-gen/src/router/js-koa.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
fileContextSetIndent,
99
} from "../file/context.js";
1010
import { fileWrite } from "../file/write.js";
11-
import { routeStructureGet } from "../processors/route-structure.js";
11+
import {
12+
routeStructureGet,
13+
routeStructureGetOpenApi,
14+
} from "../processors/route-structure.js";
1215
import { JavascriptImportCollector } from "../target/javascript.js";
1316
import { typesCacheGet } from "../types/cache.js";
1417
import {
@@ -433,7 +436,11 @@ export function jsKoaRegisterCompasStructureRoute(generateContext, file) {
433436
`compasHandlers.structure = (ctx) => {
434437
ctx.set("Content-Type", "application/json");
435438
439+
if (ctx.validatedQuery.format === "compas") {
436440
ctx.body = \`${routeStructureGet(generateContext)}\`;
441+
} else if (ctx.validatedQuery.format === "openapi") {
442+
ctx.body = \`${routeStructureGetOpenApi(generateContext)}\`;
443+
}
437444
};
438445
`,
439446
);

0 commit comments

Comments
 (0)