Skip to content

Commit

Permalink
feat: introduce responder
Browse files Browse the repository at this point in the history
  • Loading branch information
mnahkies committed Nov 4, 2023
1 parent 01367a7 commit b83d362
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {isDefined, titleCase} from "../../core/utils"
import {SchemaBuilder, schemaBuilderFactory} from "../common/schema-builders/schema-builder"
import {buildExport, requestBodyAsParameter, statusStringToType} from "../common/typescript-common"
import {OpenapiGeneratorConfig} from "../../templates.types"
import {object} from "../common/type-utils"
import {intersect, object} from "../common/type-utils"

function reduceParamsToOpenApiSchema(parameters: IRParameter[]): IRModelObject {
return parameters.reduce((acc, parameter) => {
Expand All @@ -34,7 +34,10 @@ function reduceParamsToOpenApiSchema(parameters: IRParameter[]): IRModelObject {

export class ServerBuilder {
private readonly statements: string[] = []
private readonly operationTypeMap: Record<string, string> = {}
private readonly operationTypes: {
operationId: string,
statements: string[]
}[] = []

constructor(
public readonly filename: string,
Expand All @@ -43,14 +46,17 @@ export class ServerBuilder {
private readonly imports: ImportBuilder,
public readonly types: TypeBuilder,
public readonly schemaBuilder: SchemaBuilder,
private existingRegions: { [operationId: string]: string },
private existingRegions: {
[operationId: string]: string
},
) {
// todo: unsure why, but adding an export at `.` of index.ts doesn't work properly
this.imports.from("@nahkies/typescript-koa-runtime/server")
.add(
"startServer",
"ServerConfig",
"Response",
"KoaRuntimeResponse",
"KoaRuntimeResponder",
"StatusCode2xx",
"StatusCode3xx",
"StatusCode4xx",
Expand All @@ -61,7 +67,7 @@ export class ServerBuilder {
this.imports.from("@nahkies/typescript-koa-runtime/errors")
.add(
"KoaRuntimeError",
"RequestInputType"
"RequestInputType",
)

this.imports.from("koa")
Expand Down Expand Up @@ -124,32 +130,50 @@ export class ServerBuilder {
statusType: statusStringToType(status),
type: content ? types.schemaObjectToType(content.schema) : "void",
schema: content ? schemaBuilder.fromModel(content.schema, true, true) : schemaBuilder.void(),
isWildCard: /^\d[xX]{2}$/.test(status),
})
}

return acc
}, {specific: [], defaultResponse: undefined} as {
specific: { statusString: string, statusType: string, schema: string, type: string }[], defaultResponse?: {
type: string, schema: string
specific: {
statusString: string,
statusType: string,
schema: string,
type: string,
isWildCard: boolean,
}[],
defaultResponse?: {
type: string,
schema: string
}
})

this.operationTypeMap[operation.operationId] =
buildExport({
name: titleCase(operation.operationId),
value: `
(
params: Params<${pathParamsType}, ${queryParamsType}, ${bodyParamsType + (bodyParamsType === "void" || bodyParamIsRequired ? "" : " | undefined")}>,
ctx: Context
) => Promise<${
[
...responseSchemas.specific.map(it => `Response<${it.statusType}, ${it.type}>`),
responseSchemas.defaultResponse && `Response<StatusCode, ${responseSchemas.defaultResponse.type}>`,
]
.filter(isDefined).join(" | ")
}>`,
kind: "type",
})
this.operationTypes.push({
operationId: operation.operationId,
statements: [
buildExport({
name: titleCase(operation.operationId) + "Responder",
value: intersect(object([
...responseSchemas.specific.map(it => it.isWildCard ?
`with${it.statusType}(status: ${it.statusType}): KoaRuntimeResponse<${it.type}>`
: `with${it.statusType}: KoaRuntimeResponse<${it.type}>`),
responseSchemas.defaultResponse && `withDefault(status: StatusCode): KoaRuntimeResponse<${responseSchemas.defaultResponse.type}>`,
],
), "KoaRuntimeResponder"),
kind: "type",
}),
buildExport({
name: titleCase(operation.operationId),
value: `(
params: Params<${pathParamsType}, ${queryParamsType}, ${bodyParamsType + (bodyParamsType === "void" || bodyParamIsRequired ? "" : " | undefined")}>,
respond: ${titleCase(operation.operationId) + "Responder"},
ctx: Context
) => Promise<KoaRuntimeResponse<unknown>>`,
kind: "type",
}),
],
})

this.statements.push([
`const ${operation.operationId}ResponseValidator = responseValidationFactory([${
Expand All @@ -165,8 +189,22 @@ export class ServerBuilder {
body: ${bodyParamSchema ? `parseRequestInput(${operation.operationId}BodySchema, ctx.request.body, RequestInputType.RequestBody)` : "undefined"},
}
const {status, body} = await implementation.${operation.operationId}(input, ctx)
.catch(err => { throw KoaRuntimeError.HandlerError(err) })
const response = await implementation.${operation.operationId}(input, {
${
[
...responseSchemas.specific.map(it => it.isWildCard ?
`with${it.statusType}(status: ${it.statusType}) {return new KoaRuntimeResponse<${it.type}>(status) }`
: `get with${it.statusType}() {return new KoaRuntimeResponse<${it.type}>(${it.statusType}) }`),
responseSchemas.defaultResponse && `withDefault(status: StatusCode) { return new KoaRuntimeResponse<${responseSchemas.defaultResponse.type}>(status) }`,
"withStatus(status: StatusCode) { return new KoaRuntimeResponse(status)}",
].filter(Boolean).join(",\n")
}}, ctx)
.catch(err => { throw KoaRuntimeError.HandlerError(err) })
const {
status,
body,
} = response.unpack()
ctx.body = ${operation.operationId}ResponseValidator(status, body)
ctx.status = status
Expand All @@ -186,11 +224,16 @@ ${imports.toString()}
//region safe-edit-region-header
${this.existingRegions["header"] ?? ""}
//endregion safe-edit-region-header
${Object.values(this.operationTypeMap).join("\n\n")}
${this.operationTypes.flatMap(it => it.statements).join("\n\n")}
${buildExport({
name: "Implementation",
value: object(Object.keys(this.operationTypeMap).map((key) => `${key}: ${titleCase(key)}`).join(",")),
value: object(
this.operationTypes
.map(it => it.operationId)
.map((key) => `${key}: ${titleCase(key)}`)
.join(","),
),
kind: "type",
})}
Expand Down
23 changes: 20 additions & 3 deletions packages/typescript-koa-runtime/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,26 @@ export type StatusCode =
| StatusCode4xx
| StatusCode5xx

export type Response<Status extends StatusCode, Type> = {
status: Status
body: Type
export class KoaRuntimeResponse<Type> {
private _body?: Type

constructor(private readonly status: number) {}

body(body: Type): this {
this._body = body
return this
}

unpack() {
return {status: this.status, body: this._body}
}
}

export type KoaRuntimeResponder<
Status extends StatusCode = StatusCode,
Type = any,
> = {
withStatus: (status: Status) => KoaRuntimeResponse<Type>
}

export type ServerConfig = {
Expand Down

0 comments on commit b83d362

Please sign in to comment.