Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create interface for responder so responder types take presidence over default types #27

Merged
merged 4 commits into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Ignored directories
src
tmp
example
.husky
assets
__tests__
.github
.husky
.swc
.vscode
assets
examples
src
tmp

# Ignored file types
*.tsbuildinfo
Expand Down
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["gruntfuggly.todo-tree"]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"todo-tree.regex.regex": "(//|#|<!--|;|/\\*|(\\*?\\s)|^|^[ \\t]*(-|\\d+.))\\s*($TAGS)"
}
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ npx compeller new
Create a Schema specification for an API Model like:

```ts
// ./example/openapi/schemas/version.schema.ts
// ./examples/openapi/schemas/version.schema.ts

import { FromSchema } from 'json-schema-to-ts';

Expand All @@ -51,7 +51,7 @@ export type Version = FromSchema<typeof VersionSchema>;
Next, bind the model into an OpenAPI specification object.

```ts
// ./example/openapi/spec.ts
// ./examples/openapi/spec.ts

import { VersionSchema } from './schemas/version.schema';

Expand Down Expand Up @@ -95,6 +95,8 @@ const { response } = api('/v1/version', 'get');
const resp = response('200', { name: 'Type-safe reply' });
```

See [./examples](./examples)

## Shoulders

Compell is built on top of some great libraries, at it's core it relies on:
Expand Down
12 changes: 12 additions & 0 deletions examples/openapi/api-gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { APIGatewayV1Responder, compeller } from '../../src';
import { OpenAPISpecification } from './spec';

const apiGatewayV1Compeller = compeller(OpenAPISpecification, {
responder: APIGatewayV1Responder,
});

console.info(
apiGatewayV1Compeller('v1/version', 'get').response('200', {
version: '1.0.0',
})
);
22 changes: 22 additions & 0 deletions examples/openapi/custom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { compeller } from '../../src';
import { OpenAPISpecification } from './spec';

const customerCompeller = compeller(OpenAPISpecification, {
responder: (statusCode, body) => {
return typeof statusCode === 'string'
? {
statusCode: parseInt(statusCode),
body: JSON.stringify(body),
}
: {
statusCode,
body: JSON.stringify(body),
};
},
});

console.info(
customerCompeller('v1/version', 'get').response('200', {
version: '1.0.0',
})
);
10 changes: 10 additions & 0 deletions examples/openapi/default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { compeller } from '../../src';
import { OpenAPISpecification } from './spec';

const defaultCompeller = compeller(OpenAPISpecification);

console.info(
defaultCompeller('v1/version', 'get').response('200', {
version: '1.0.0',
})
);
2 changes: 1 addition & 1 deletion example/openapi/spec.ts → examples/openapi/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const OpenAPISpecification = {
},
openapi: '3.1.0',
paths: {
'v1//version': {
'v1/version': {
get: {
responses: {
'200': {
Expand Down
2 changes: 2 additions & 0 deletions src/compeller/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { join } from 'path';
import { compeller } from '.';
import { defaultResponder } from '..';

const spec = {
info: {
Expand Down Expand Up @@ -52,6 +53,7 @@ describe('API Compiler tests', () => {
it('keeps a local specification json when true', () => {
const stuff = compeller(spec, {
jsonSpecFile: join(__dirname, 'tmp', 'openapi.json'),
responder: defaultResponder,
});

const { response } = stuff('/test', 'get');
Expand Down
26 changes: 14 additions & 12 deletions src/compeller/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Ajv, { JSONSchemaType } from 'ajv';
import { FromSchema } from 'json-schema-to-ts';
import { OpenAPIObject } from 'openapi3-ts';

import { defaultResponder } from './responders';
import { writeSpecification } from './file-utils/write-specification';

export interface ICompellerOptions {
Expand All @@ -24,10 +26,11 @@ export interface ICompellerOptions {
* The responder formats the response of an adapter. Without a responder, the
* statusCode and response body are returned in an object
*/
responder?: ({}: {
statusCode: string | number | symbol;
body: unknown;
}) => any;
responder: <T extends string | number | symbol, U>(
statusCode: T,
body: U,
...args: any
) => any;
}

/**
Expand All @@ -42,6 +45,7 @@ export interface ICompellerOpenAPIObject extends OpenAPIObject {}
const DEFAULT_OPTIONS: ICompellerOptions = {
contentType: 'application/json',
jsonSpecFile: false,
responder: defaultResponder,
};

/**
Expand All @@ -63,7 +67,7 @@ export const compeller = <
{
contentType = 'application/json',
jsonSpecFile = false,
responder,
responder = defaultResponder,
}: ICompellerOptions = DEFAULT_OPTIONS
) => {
if (jsonSpecFile) {
Expand All @@ -81,6 +85,8 @@ export const compeller = <
const path = route as string;

/**
* TODO - Responders need to handle headers
*
* Build a response object for the API with the required status and body
* format
*
Expand All @@ -97,16 +103,12 @@ export const compeller = <
statusCode: R,
body: FromSchema<SC>
) => {
if (!responder)
return {
statusCode,
body,
};

return responder({ statusCode, body });
return responder<R, FromSchema<SC>>(statusCode, body);
};

/**
* TODO - Validators need to be abstracted like responders
*
* The request validator attaches request body validation to the request
* handler for a path.
*
Expand Down
19 changes: 8 additions & 11 deletions src/compeller/responders/aws/api-gateway-v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { APIGatewayV1Responder } from './api-gateway-v1';

describe('APIGatewayV1Responder', () => {
it('maintains the API Gateway interface', () => {
const responder = APIGatewayV1Responder<200, { name: string }>({
statusCode: 200,
body: {
name: 'Simon',
},
const responder = APIGatewayV1Responder<200, { name: string }>(200, {
name: 'Simon',
});

expect(responder).toEqual({
Expand All @@ -16,15 +13,15 @@ describe('APIGatewayV1Responder', () => {
});

it('allows for header injection', () => {
const responder = APIGatewayV1Responder<200, { name: string }>({
statusCode: 200,
body: {
const responder = APIGatewayV1Responder<200, { name: string }>(
200,
{
name: 'Simon',
},
headers: {
{
'x-sample-header': 'sample;',
},
});
}
);

expect(responder).toEqual({
statusCode: 200,
Expand Down
24 changes: 11 additions & 13 deletions src/compeller/responders/aws/api-gateway-v1.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { APIGatewayProxyResult } from 'aws-lambda';
import { ICompellerOptions } from '../..';

export const APIGatewayV1Responder = <T, U>({
statusCode,
body,
headers,
isBase64Encoded,
multiValueHeaders,
}: {
statusCode: string | number | symbol;
body: U;
} & Omit<
APIGatewayProxyResult,
'statusCode' | 'body'
>): APIGatewayProxyResult => {
export const APIGatewayV1Responder: ICompellerOptions['responder'] = <
T extends string | number | symbol,
U
>(
statusCode: T,
body: U,
headers: APIGatewayProxyResult['headers'],
isBase64Encoded: APIGatewayProxyResult['isBase64Encoded'],
multiValueHeaders: APIGatewayProxyResult['multiValueHeaders']
): APIGatewayProxyResult => {
if (typeof statusCode === 'number') {
return {
statusCode,
Expand Down
9 changes: 9 additions & 0 deletions src/compeller/responders/default/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ICompellerOptions } from '../..';

export const defaultResponder: ICompellerOptions['responder'] = <T, U>(
statusCode: T,
body: U
) => ({
statusCode,
body,
});
1 change: 1 addition & 0 deletions src/compeller/responders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
* of the response but responder would still support forms of injection.
*/

export * from './default';
export * from './aws/api-gateway-v1';