Skip to content

Commit

Permalink
middy generator
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbarbour committed Nov 21, 2024
1 parent 82853a7 commit 692f969
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 943 deletions.
1,023 changes: 120 additions & 903 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"build": "npm run build:esm && npm run build:cjs && chmod +x ./dist/esm/cli/index.js",
"generate:middy": "./dist/esm/cli/index.js generate --template middy --apiVersion 1.0.0 $(pwd)/test/schema-example.ts",
"generate:hexlabs": "./dist/esm/cli/index.js generate --template hexlabs --apiVersion 1.0.0 $(pwd)/test/schema-example.ts",
"test": "npm run generate:hexlabs && jest --ci --runInBand --coverage --reporters=default --reporters=jest-junit --passWithNoTests",
"test": "npm run generate:middy && jest --ci --runInBand --coverage --reporters=default --reporters=jest-junit --passWithNoTests",
"lint": "eslint **/*.ts"
},
"eslintConfig": {
Expand Down Expand Up @@ -101,17 +101,17 @@
"@hexlabs/http-api-ts": "^2",
"@hexlabs/lambda-api-ts": "^0",
"@middy/core": "^5",
"@middy/http-json-body-parser": "^5",
"@middy/http-router": "^5",
"@middy/validator": "^5"
"@aws-lambda-powertools/logger": "^2",
"@aws-lambda-powertools/parser": "^"
},
"devDependencies": {
"@aws-lambda-powertools/logger": "^2.10.0",
"@aws-lambda-powertools/parser": "^2.10.0",
"@hexlabs/http-api-ts": "^2.0.36",
"@types/aws-lambda": "^8.10.40",
"@middy/core": "^5",
"@middy/http-json-body-parser": "^5",
"@middy/http-router": "^5",
"@middy/validator": "^5",
"@types/aws-lambda": "^8.10.40",
"@types/ejs": "^3.1.5",
"@types/jest": "^24.9.1",
"@types/node": "^18.18.5",
Expand All @@ -124,7 +124,8 @@
"jest-junit": "^10.0.0",
"ts-jest": "^29.1.1",
"tslib": "^2.6.2",
"typescript": "^4.7.4"
"typescript": "^4.7.4",
"zod-to-json-schema": "^3.23.5"
},
"dependencies": {
"chalk": "^2.4.2",
Expand Down
16 changes: 12 additions & 4 deletions src/cli/generators/middy/middy-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,40 @@ export class MiddyGenerator implements ApiGenerator {

private routesFunction(routes: string[]): string {
return '' +
`routes(): Route<APIGatewayProxyEvent, APIGatewayProxyResult>[] {
`routes(): Route<any, APIGatewayProxyResult>[] {
return [${routes.join(",")}];
}`;
}

private imports() {
return `import middy from '@middy/core';
import httpJsonBodyParser from '@middy/http-json-body-parser';
import { APIGatewayProxyEventSchema } from '@aws-lambda-powertools/parser/schemas';
import { Logger } from '@aws-lambda-powertools/logger';
import { parser } from '@aws-lambda-powertools/parser/middleware';
import { Route } from '@middy/http-router';
import validatorMiddleware from '@middy/validator';
import { APIGatewayEvent, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import schema from './schema.json' with { type: 'json' };
import * as model from './model';`
import * as model from './zod-model';
import * as z from 'zod';`
}

private apiClassString(className: string, operations: { path: Path, method: Method }[]): string {
const routes = operations.map(operation => route(operation.path, operation.method));
return '' +
`${this.imports()}
${[...new Set(operations.filter(it => !!it.method.requestType).map(it => it.method.requestType))]
.map(requestType => `const ${requestType}Event = APIGatewayProxyEventSchema.extend({ detail: model.${requestType} });`)
.join('\n')}
export interface ${className}Handlers {
${operations.map(operation => handlerMethodType(operation.method)).join('\n ')}
}
export class ${className} {
private logger = new Logger({ serviceName: '${className}' });
${this.routesFunction(routes)}
public readonly handlers: Partial<${className}Handlers> = {};
Expand Down
8 changes: 4 additions & 4 deletions src/cli/generators/middy/templates/middy-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Method } from '../../../paths/method';
import { Path } from '../../../paths/path';

function eventType(method: Method): string {
return method.requestType ? `APIGatewayEvent & { body: model.${method.requestType} }` : 'APIGatewayEvent';
return method.requestType ? `z.infer<typeof ${method.requestType}Event>` : 'APIGatewayEvent';
}

export function handlerMethodType(method: Method): string {
Expand All @@ -18,11 +18,11 @@ export function route(path: Path, method: Method): string {
}

export function handlerFunction(method: Method): string {
const validator = method.requestType ? `.use(validatorMiddleware({eventSchema: () => schema.components.schemas.${method.requestType} }))\n `: '';

const validator = method.requestType ? `.use(parser({ schema: ${method.requestType}Event }))\n `: '';
return '' +
`${method.operationId}() {
return middy()
${validator}.use(httpJsonBodyParser<${eventType(method)}>())
.handler(this.handlers.${method.operationId}.bind(this))
${validator}.handler(this.handlers.${method.operationId}.bind(this))
}`;
}
2 changes: 2 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function generatorFor(template: 'hexlabs' | 'middy'): ApiGenerator {
async function generateFromSchema(schemaLocation: string, command: any) {
const {template, apiVersion} = command;
const schema: OAS = schemaLocation.endsWith('.ts') ? (await import(schemaLocation)).default : (await import(schemaLocation));
const location = schemaLocation.substring(0, schemaLocation.lastIndexOf('/'));
const dir = 'generated/' + schema.info.title.toLowerCase().replace(/ /g, '-') + '/';
if(!fs.existsSync(dir)) fs.mkdirSync(dir, {recursive: true});
const pathFinder = PathFinder.from(schema);
Expand All @@ -46,6 +47,7 @@ async function generateFromSchema(schemaLocation: string, command: any) {
fs.writeFileSync(dir + 'schema.json', JSON.stringify({ components: { schemas: schema.components?.schemas ?? {} } }, null, 2));
fs.writeFileSync(dir + 'oas.json', JSON.stringify(schema, null, 2));
if(schema.servers) fs.writeFileSync(dir + 'servers.json', JSON.stringify(schema.servers, null, 2));
fs.writeFileSync(dir + 'zod-model.ts', fs.readFileSync(location + '/model.ts'));
fs.writeFileSync(dir + 'model.ts', await types(schema));
fs.writeFileSync(dir + 'sdk.ts', generateSdkFrom(schema, apiVersion));
}
Expand Down
21 changes: 21 additions & 0 deletions src/schema-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as z from 'zod';
import { JsonSchema7Type, zodToJsonSchema } from "zod-to-json-schema";

export class ZodSchemaBuilder<R extends Record<string, JsonSchema7Type>> {

private constructor(private readonly schemas: R) {}

static create(): ZodSchemaBuilder<{}> {
return new ZodSchemaBuilder({});
}

add<S extends string, T extends z.ZodType>(name: S, type: T): ZodSchemaBuilder<R & {[name in S]: JsonSchema7Type}> {
this.schemas[name] = (zodToJsonSchema(type, {name, basePath: ['#','components', 'schemas'] }).definitions as any)[name];
this.schemas[name].title = name;
return this as any;
}

build(): { components: { schemas: R } } {
return { components: { schemas: this.schemas } };
}
}
18 changes: 18 additions & 0 deletions test/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as z from 'zod';

export const Chicken = z.object({
identifier: z.string(),
type: z.string(),
name: z.string()
});
export type Chicken = z.infer<typeof Chicken>

export const ChickenCollection = z.array(Chicken);
export type ChickenCollection = z.infer<typeof ChickenCollection>

export const ChickenCreateRequest = z.object({
type: z.string(),
name: z.string()
});

export type ChickenCreateRequest = z.infer<typeof ChickenCreateRequest>
32 changes: 8 additions & 24 deletions test/schema-example.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {OpenApiSpecificationBuilder, SchemaBuilder, OASServer} from "../src";

const builder = SchemaBuilder.create();
import {OpenApiSpecificationBuilder, OASServer} from "../src";
import { ZodSchemaBuilder } from '../src/schema-builder';
import * as model from './model';

const servers: OASServer[] = [
{
Expand All @@ -16,21 +16,11 @@ const servers: OASServer[] = [
},
];

const schemas = builder.add('Chicken', s => s.object({
identifier: s.string(),
type: s.string(),
name: s.string()
}))
.add('ChickenCollection', s => s.array(s.reference('Chicken')))
.add('Schema', s => s.object(undefined, undefined, true))
.add('ChickenCreateRequest', s => s.object({
type: s.string(),
name: s.string()
}))
.add('ChickenCreateRequest2', s => s.object({}, {
type: s.string(),
name: s.string()
})).build();
const schemas = ZodSchemaBuilder.create()
.add("Chicken", model.Chicken)
.add("ChickenCollection", model.ChickenCollection)
.add("ChickenCreateRequest", model.ChickenCreateRequest)
.build()

export default OpenApiSpecificationBuilder
.create(schemas, { title: 'Chicken Store API', version: '1.0.0'})
Expand Down Expand Up @@ -97,12 +87,6 @@ export default OpenApiSpecificationBuilder
o.header('X-Encryption-Key')
]
},
},
'/schema': {
get: {
operationId: 'getSchema',
responses: { 200: {description:'Schema', content: o.jsonContent('Schema') } }
}
}
}))
.build();
2 changes: 1 addition & 1 deletion test/schema-simple-example.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {OpenApiSpecificationBuilder, SchemaBuilder} from "../dist";
import {OpenApiSpecificationBuilder, SchemaBuilder} from "../src";

const builder = SchemaBuilder.create();

Expand Down

0 comments on commit 692f969

Please sign in to comment.