Skip to content

Commit 2d7d0b0

Browse files
committed
refactor: split files apart
1 parent eee7c85 commit 2d7d0b0

6 files changed

Lines changed: 305 additions & 296 deletions

File tree

packages/openapi-code-generator/src/typescript/server/abstract-server-router-builder.ts renamed to packages/openapi-code-generator/src/typescript/server/abstract-router-builder.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@ import type {SchemaBuilder} from "../common/schema-builders/schema-builder"
66
import type {TypeBuilder} from "../common/type-builder"
77
import {ServerOperationBuilder} from "./server-operation-builder"
88

9-
export abstract class AbstractServerRouterBuilder implements ICompilable {
9+
export type ServerSymbols = {
10+
implPropName: string
11+
implTypeName: string
12+
responderName: string
13+
paramSchema: string
14+
querySchema: string
15+
requestBodySchema: string
16+
requestHeaderSchema: string
17+
responseBodyValidator: string
18+
}
19+
20+
export abstract class AbstractRouterBuilder implements ICompilable {
1021
private readonly statements: string[] = []
1122

1223
protected constructor(
@@ -40,6 +51,8 @@ export abstract class AbstractServerRouterBuilder implements ICompilable {
4051
statements: string[],
4152
): string
4253

54+
protected abstract operationSymbols(operationId: string): ServerSymbols
55+
4356
toString(): string {
4457
return this.buildRouter(this.name, this.statements)
4558
}

packages/openapi-code-generator/src/typescript/server/server-operation-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
requestBodyAsParameter,
1313
statusStringToType,
1414
} from "../common/typescript-common"
15-
import type {ServerSymbols} from "./typescript-koa/typescript-koa.generator"
15+
import type {ServerSymbols} from "./abstract-router-builder"
1616

1717
export function reduceParamsToOpenApiSchema(
1818
parameters: IRParameter[],

packages/openapi-code-generator/src/typescript/server/typescript-koa/typescript-koa.generator.spec.ts renamed to packages/openapi-code-generator/src/typescript/server/typescript-koa/typescript-koa-router-builder.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {ImportBuilder} from "../../common/import-builder"
55
import {schemaBuilderFactory} from "../../common/schema-builders/schema-builder"
66
import {TypeBuilder} from "../../common/type-builder"
77
import {TypescriptFormatterBiome} from "../../common/typescript-formatter.biome"
8-
import {KoaServerRouterBuilder} from "./typescript-koa.generator"
8+
import {KoaRouterBuilder} from "./typescript-koa-router-builder"
99

10-
describe("typescript/typescript-koa", () => {
11-
describe("ServerRouterBuilder#implementationExport", () => {
10+
describe("typescript/server/typescript-koa/koa-router-builder", () => {
11+
describe("#implementationExport", () => {
1212
it("can output implementation types as a `export type`", async () => {
1313
const actual = await getActual("type")
1414
// TODO: check result is actually valid typescript
@@ -65,7 +65,7 @@ describe("typescript/typescript-koa", () => {
6565
"zod",
6666
{allowAny: true},
6767
)
68-
const serverRouterBuilder = new KoaServerRouterBuilder(
68+
const serverRouterBuilder = new KoaRouterBuilder(
6969
"unit-test.ts",
7070
"unit-test",
7171
input,
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import type {Input} from "../../../core/input"
2+
import {isDefined, titleCase} from "../../../core/utils"
3+
import type {ServerImplementationMethod} from "../../../templates.types"
4+
import type {ImportBuilder} from "../../common/import-builder"
5+
import {JoiBuilder} from "../../common/schema-builders/joi-schema-builder"
6+
import type {SchemaBuilder} from "../../common/schema-builders/schema-builder"
7+
import {ZodBuilder} from "../../common/schema-builders/zod-schema-builder"
8+
import type {TypeBuilder} from "../../common/type-builder"
9+
import {constStatement, object} from "../../common/type-utils"
10+
import {buildExport} from "../../common/typescript-common"
11+
import {
12+
AbstractRouterBuilder,
13+
type ServerSymbols,
14+
} from "../abstract-router-builder"
15+
import type {ServerOperationBuilder} from "../server-operation-builder"
16+
17+
export class KoaRouterBuilder extends AbstractRouterBuilder {
18+
private readonly operationTypes: {
19+
operationId: string
20+
statements: string[]
21+
}[] = []
22+
23+
constructor(
24+
filename: string,
25+
name: string,
26+
input: Input,
27+
imports: ImportBuilder,
28+
types: TypeBuilder,
29+
schemaBuilder: SchemaBuilder,
30+
private readonly implementationMethod: ServerImplementationMethod,
31+
) {
32+
super(filename, name, input, imports, types, schemaBuilder)
33+
}
34+
35+
protected buildImports(): void {
36+
// todo: unsure why, but adding an export at `.` of index.ts doesn't work properly
37+
this.imports
38+
.from("@nahkies/typescript-koa-runtime/server")
39+
.add(
40+
"KoaRuntimeResponder",
41+
"KoaRuntimeResponse",
42+
"Params",
43+
"Response",
44+
"ServerConfig",
45+
"StatusCode",
46+
"StatusCode2xx",
47+
"StatusCode3xx",
48+
"StatusCode4xx",
49+
"StatusCode5xx",
50+
"startServer",
51+
)
52+
53+
this.imports
54+
.from("@nahkies/typescript-koa-runtime/errors")
55+
.add("KoaRuntimeError", "RequestInputType")
56+
57+
this.imports.addModule("KoaRouter", "@koa/router")
58+
this.imports.from("@koa/router").add("RouterContext")
59+
60+
if (this.schemaBuilder instanceof ZodBuilder) {
61+
this.imports
62+
.from("@nahkies/typescript-koa-runtime/zod")
63+
.add("parseRequestInput", "responseValidationFactory")
64+
} else if (this.schemaBuilder instanceof JoiBuilder) {
65+
this.imports
66+
.from("@nahkies/typescript-koa-runtime/joi")
67+
.add("parseRequestInput", "responseValidationFactory")
68+
}
69+
}
70+
71+
protected buildOperation(builder: ServerOperationBuilder): string {
72+
const statements: string[] = []
73+
74+
const symbols = this.operationSymbols(builder.operationId)
75+
const params = builder.parameters(symbols)
76+
77+
if (params.path.schema) {
78+
statements.push(constStatement(symbols.paramSchema, params.path.schema))
79+
}
80+
if (params.query.schema) {
81+
statements.push(constStatement(symbols.querySchema, params.query.schema))
82+
}
83+
if (params.header.schema) {
84+
statements.push(
85+
constStatement(symbols.requestHeaderSchema, params.header.schema),
86+
)
87+
}
88+
if (params.body.schema) {
89+
statements.push(
90+
constStatement(symbols.requestBodySchema, params.body.schema),
91+
)
92+
}
93+
94+
const responseSchemas = builder.responseSchemas()
95+
const responder = builder.responder()
96+
97+
this.operationTypes.push({
98+
operationId: builder.operationId,
99+
statements: [
100+
buildExport({
101+
name: symbols.responderName,
102+
value: responder.type,
103+
kind: "type",
104+
}),
105+
buildExport({
106+
name: symbols.implTypeName,
107+
value: `(
108+
params: ${params.type},
109+
respond: ${symbols.responderName},
110+
ctx: RouterContext
111+
) => Promise<KoaRuntimeResponse<unknown> | ${[
112+
...responseSchemas.specific.map(
113+
(it) => `Response<${it.statusType}, ${it.type}>`,
114+
),
115+
responseSchemas.defaultResponse &&
116+
`Response<StatusCode, ${responseSchemas.defaultResponse.type}>`,
117+
]
118+
.filter(isDefined)
119+
.join(" | ")}>`,
120+
kind: "type",
121+
}),
122+
],
123+
})
124+
125+
statements.push(`
126+
const ${symbols.responseBodyValidator} = ${builder.responseValidator()}
127+
128+
router.${builder.method.toLowerCase()}('${symbols.implPropName}','${route(builder.route)}', async (ctx, next) => {
129+
const input = {
130+
params: ${params.path.schema ? `parseRequestInput(${symbols.paramSchema}, ctx.params, RequestInputType.RouteParam)` : "undefined"},
131+
query: ${params.query.schema ? `parseRequestInput(${symbols.querySchema}, ctx.query, RequestInputType.QueryString)` : "undefined"},
132+
body: ${params.body.schema ? `parseRequestInput(${symbols.requestBodySchema}, Reflect.get(ctx.request, "body"), RequestInputType.RequestBody)` : "undefined"},
133+
headers: ${params.header.schema ? `parseRequestInput(${symbols.requestHeaderSchema}, Reflect.get(ctx.request, "headers"), RequestInputType.RequestHeader)` : "undefined"}
134+
}
135+
136+
const responder = ${responder.implementation}
137+
138+
const response = await implementation.${symbols.implPropName}(input, responder, ctx)
139+
.catch(err => { throw KoaRuntimeError.HandlerError(err) })
140+
141+
const { status, body } = response instanceof KoaRuntimeResponse ? response.unpack() : response
142+
143+
ctx.body = ${symbols.responseBodyValidator}(status, body)
144+
ctx.status = status
145+
return next();
146+
})`)
147+
148+
return statements.join("\n\n")
149+
}
150+
151+
protected operationSymbols(operationId: string): ServerSymbols {
152+
return {
153+
implPropName: operationId,
154+
implTypeName: titleCase(operationId),
155+
responderName: `${titleCase(operationId)}Responder`,
156+
paramSchema: `${operationId}ParamSchema`,
157+
querySchema: `${operationId}QuerySchema`,
158+
requestBodySchema: `${operationId}BodySchema`,
159+
requestHeaderSchema: `${operationId}HeaderSchema`,
160+
responseBodyValidator: `${operationId}ResponseValidator`,
161+
}
162+
}
163+
164+
implementationExport(name: string): string {
165+
switch (this.implementationMethod) {
166+
case "type":
167+
case "interface": {
168+
return buildExport({
169+
name,
170+
value: object(
171+
this.operationTypes
172+
.map((it) => this.operationSymbols(it.operationId))
173+
.map((it) => `${it.implPropName}: ${it.implTypeName}`)
174+
.join(","),
175+
),
176+
kind: this.implementationMethod,
177+
})
178+
}
179+
180+
case "abstract-class": {
181+
return buildExport({
182+
name,
183+
value: object(
184+
this.operationTypes
185+
.map((it) => this.operationSymbols(it.operationId))
186+
.map((it) => `abstract ${it.implPropName}: ${it.implTypeName}`)
187+
.join("\n"),
188+
),
189+
kind: "abstract-class",
190+
})
191+
}
192+
193+
default: {
194+
throw new Error(
195+
`server implementation method '${this.implementationMethod}' is not supported`,
196+
)
197+
}
198+
}
199+
}
200+
201+
protected buildRouter(
202+
routerName: string,
203+
routerStatements: string[],
204+
): string {
205+
const moduleName = titleCase(routerName)
206+
const implementationExportName = `${moduleName}Implementation`
207+
const createRouterExportName = `create${moduleName}Router`
208+
209+
return `
210+
${this.operationTypes.flatMap((it) => it.statements).join("\n\n")}
211+
212+
${this.implementationExport(implementationExportName)}
213+
214+
export function ${createRouterExportName}(implementation: ${implementationExportName}): KoaRouter {
215+
const router = new KoaRouter()
216+
217+
${routerStatements.join("\n\n")}
218+
219+
return router
220+
}
221+
222+
${
223+
moduleName &&
224+
`
225+
export {${createRouterExportName} as createRouter}
226+
export ${this.implementationMethod === "type" || this.implementationMethod === "interface" ? "type" : ""} {${implementationExportName} as Implementation}
227+
`
228+
}
229+
`
230+
}
231+
}
232+
233+
function route(route: string): string {
234+
const placeholder = /{([^{}]+)}/g
235+
236+
return Array.from(route.matchAll(placeholder)).reduce((result, match) => {
237+
return result.replace(match[0], `:${match[1]}`)
238+
}, route)
239+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type {Input} from "../../../core/input"
2+
import {CompilationUnit, type ICompilable} from "../../common/compilation-units"
3+
import {ImportBuilder} from "../../common/import-builder"
4+
5+
export class KoaServerBuilder implements ICompilable {
6+
constructor(
7+
public readonly filename: string,
8+
private readonly name: string,
9+
private readonly input: Input,
10+
private readonly imports: ImportBuilder = new ImportBuilder(),
11+
) {
12+
this.imports
13+
.from("@nahkies/typescript-koa-runtime/server")
14+
.add(
15+
"startServer",
16+
"ServerConfig",
17+
"Response",
18+
"KoaRuntimeResponse",
19+
"KoaRuntimeResponder",
20+
"StatusCode2xx",
21+
"StatusCode3xx",
22+
"StatusCode4xx",
23+
"StatusCode5xx",
24+
"StatusCode",
25+
)
26+
}
27+
28+
toString(): string {
29+
return `
30+
export async function bootstrap(config: ServerConfig) {
31+
// ${this.name}
32+
return startServer(config)
33+
}
34+
`
35+
}
36+
37+
toCompilationUnit(): CompilationUnit {
38+
return new CompilationUnit(this.filename, this.imports, this.toString())
39+
}
40+
}

0 commit comments

Comments
 (0)