Skip to content

Commit

Permalink
Merge pull request #64 from lifeomic/PML-163
Browse files Browse the repository at this point in the history
  • Loading branch information
david-wb authored May 2, 2023
2 parents 1a16269 + b429551 commit 434f0f5
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 6 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ const app = new Koa().use(router.middleware());
app.listen();
```

In case the main router requires authorization headers, and you want to query the introspection route without them, you can expose it on a custom router
like so:

```typescript
const router = OneSchemaRouter.create({
using: new Router(),
introspection: {
route: '/introspection',
router: new Router({ prefix: '/private' }),
serviceVersion: process.env.LIFEOMIC_SERVICE_VERSION,
},
})
.declare({
route: 'POST /items',
name: 'createItem',
request: z.object({ message: z.string() }),
response: z.object({ id: z.string(), message: z.string() }),
})
.declare({
route: 'GET /items/:id',
name: 'getItemById',
request: z.object({ filter: z.string() }),
response: z.object({ id: z.string(), message: z.string() }),
});
```

Once you have routes declared, add implementations for each route. Enjoy perfect type inference and auto-complete for path parameters, query parameters, and the request body.

```typescript
Expand Down
2 changes: 1 addition & 1 deletion src/koa.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ test('router typing is inferred correctly', () => {
parse: () => null as any,
on: router,
implementation: {
'GET /dummy-route': (ctx) => {
'GET /dummy-route': (ctx: any) => {
// assert state is extended correctly
ctx.state.dummyStateProperty;

Expand Down
7 changes: 6 additions & 1 deletion src/koa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export type IntrospectionConfig = {
* The current version of the service, served as part of introspection.
*/
serviceVersion: string;
/**
* An optional alternative router to use for the introspection route.
*/
router?: Router<any, any>;
};

/**
Expand Down Expand Up @@ -123,7 +127,8 @@ export const implementSchema = <
}: ImplementationConfig<Schema, RouterType>,
): void => {
if (introspection) {
router.get(introspection.route, (ctx, next) => {
const introRouter = introspection.router || router;
introRouter.get(introspection.route, (ctx, next) => {
const response: IntrospectionResponse = {
schema,
serviceVersion: introspection.serviceVersion,
Expand Down
59 changes: 58 additions & 1 deletion src/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,63 @@ test('introspection', async () => {
});
});

test('introspection with custom router', async () => {
const { client } = setup(() =>
OneSchemaRouter.create({
using: new Router(),
introspection: {
route: '/private/introspection',
serviceVersion: '123',
router: new Router({ prefix: '/custom' }),
},
})
.declare({
name: 'getSomething',
route: 'GET /something/:id',
description: 'it gets something',
request: z.object({ filter: z.string() }),
response: z.object({ message: z.string(), id: z.string() }),
})
.implement('GET /something/:id', () => ({ id: '', message: '' })),
);

const wrongIntroRouter = await client.get('/private/introspection');
expect(wrongIntroRouter.status).toStrictEqual(404);

const result = await client.get('/custom/private/introspection');

expect(result.data).toStrictEqual({
serviceVersion: '123',
schema: {
Endpoints: {
'GET /something/:id': {
Description: 'it gets something',
Name: 'getSomething',
Request: {
$schema: 'http://json-schema.org/draft-07/schema#',
additionalProperties: false,
properties: {
filter: { type: 'string' },
},
required: ['filter'],
type: 'object',
},
Response: {
$schema: 'http://json-schema.org/draft-07/schema#',
additionalProperties: false,
properties: {
id: { type: 'string' },
message: { type: 'string' },
},
required: ['message', 'id'],
type: 'object',
},
},
},
},
});
});

describe('type inference', () => {
test('type inference for implementation return type', () => {
setup((router) =>
Expand Down Expand Up @@ -311,7 +368,7 @@ describe('implementations', () => {
response: z.object({ message: z.string() }),
})
.implement(`${method} /items`, (ctx) => ({
message: ctx.request.query.message + '-response',
message: (ctx.request.query.message as string) + '-response',
})),
);

Expand Down
18 changes: 15 additions & 3 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ export class OneSchemaRouter<

private constructor(
private schema: Schema,
{ introspection, using: router }: OneSchemaRouterConfig<R>,
private config: OneSchemaRouterConfig<R>,
) {
const { introspection, using: router } = config;
this.router = router;

if (introspection) {
router.get(introspection.route, (ctx, next) => {
const introRouter = introspection.router || router;

introRouter.get(introspection.route, (ctx, next) => {
const response: IntrospectionResponse = {
serviceVersion: introspection.serviceVersion,
schema: convertRouterSchemaToJSONSchemaStyle(this.schema),
Expand Down Expand Up @@ -111,7 +115,15 @@ export class OneSchemaRouter<
}

middleware(): Router.Middleware {
return compose([this.router.routes(), this.router.allowedMethods()]);
const middlewares = [this.router.routes(), this.router.allowedMethods()];

if (this.config.introspection?.router) {
middlewares.push(
this.config.introspection.router.routes(),
this.config.introspection.router.allowedMethods(),
);
}
return compose(middlewares);
}
}

Expand Down

0 comments on commit 434f0f5

Please sign in to comment.