Skip to content

Commit

Permalink
Merge pull request #13 from lifeomic/use-query-property
Browse files Browse the repository at this point in the history
feat: store input in query parameter for GET/DELETE
  • Loading branch information
swain authored May 31, 2022
2 parents 1c93eb6 + 8a2c0d8 commit 2dd53d4
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 24 deletions.
109 changes: 99 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,33 @@ Endpoints:
message: { type: string }
Response:
$ref: '#/definitions/Post'
GET /posts:
Name: listPosts
Request:
type: object
properties:
filter: { type: string }
Response:
type: array
items:
$ref: '#/definitions/Post'
```
Next, run some generation.
### Schema Assumptions
`one-schema` provides a set of JSONSchema assumptions to help simplify Request/Response JSONSchema entries in commonly desired ways.

These assumptions are described by the `SchemaAssumptions` type in [`src/meta-schema.ts`](src/meta-schema.ts) and can be individually or wholly disabled in the Node API and at the command line via the `--asssumptions` flag.

### Axios Client Generation

Use the `generate-axios-client` command to generate a nicely typed Axios-based client.
Use the `generate-axios-client` command to generate a nicely typed Axios-based client from the schema.

```
one-schema generate-axios-client \
--schema schema.yml \
--output generated-client.ts \
--assumptions all \
--format
```
Expand All @@ -59,8 +74,16 @@ export type Endpoints = {
Request: {
message: string;
};
PathParams: {};
Response: Post;
};
'GET /posts': {
Request: {
filter: string;
};
PathParams: {};
Response: Post[];
};
};
export type Post = {
Expand All @@ -80,7 +103,8 @@ export class Client {
constructor(private readonly client: AxiosInstance) {}
createPost(
data: Endpoints['POST /posts']['Request'],
data: Endpoints['POST /posts']['Request'] &
Endpoints['POST /posts']['PathParams'],
config?: AxiosRequestConfig,
): Promise<AxiosResponse<Endpoints['POST /posts']['Response']>> {
return this.client.request({
Expand All @@ -90,6 +114,19 @@ export class Client {
url: substituteParams('/posts', data),
});
}
listPosts(
params: Endpoints['GET /posts']['Request'] &
Endpoints['GET /posts']['PathParams'],
config?: AxiosRequestConfig,
): Promise<AxiosResponse<Endpoints['GET /posts']['Response']>> {
return this.client.request({
...config,
method: 'GET',
params: removePathParams('/posts', params),
url: substituteParams('/posts', params),
});
}
}
```

Expand All @@ -111,6 +148,19 @@ console.log(response.data);
// id: 'some-id',
// message: 'some-message'
// }

const response = await client.listPosts({
filter: 'some-filter',
});

console.log(response.data);
// [
// {
// id: 'some-id',
// message: 'some-message'
// },
// ...
// ]
```

### API Type Generation
Expand All @@ -121,6 +171,7 @@ Use the `generate-api-types` command to generate helpful types to use for server
one-schema generate-api-types \
--schema schema.yml \
--output generated-api.ts \
--assumptions all \
--format
```

Expand All @@ -135,8 +186,16 @@ export type Endpoints = {
Request: {
message: string;
};
PathParams: {};
Response: Post;
};
'GET /posts': {
Request: {
filter: string;
};
PathParams: {};
Response: Post[];
};
};

export type Post = {
Expand Down Expand Up @@ -176,12 +235,19 @@ implementSchema(Schema, {
},
implementation: {
'POST /posts': (ctx) => {
// `ctx.request.body` is well-typed and has been run-time-validated.
// `ctx.request.body` is well-typed and has been run-time validated.
console.log(ctx.request.body.message);

// TypeScript enforces that this matches the `Response` schema.
return { id: '123', message: 'test message' };
},
'GET /posts': (ctx) => {
// `ctx.request.query` is well-typed and has been run-time validated
console.log(ctx.request.query.filter);

// TypeScript enforces that this matches the `Response` schema.
return [{ id: '123', message: 'test message' }];
},
},
});

Expand All @@ -199,6 +265,7 @@ Use the `generate-open-api-spec` command to generate an OpenAPI spec from a simp
one-schema generate-open-api-spec \
--schema schema.yml \
--output openapi-schema.json \
--assumptions all \
--apiVersion "1.0.0" \
--apiTitle "Simple API" \
--format
Expand Down Expand Up @@ -263,14 +330,36 @@ The output (in `generated-openapi-schema.json`):
}
}
}
},
"get": {
"operationId": "listPosts",
"responses": {
"200": {
"description": "TODO",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
}
}
},
"parameters": [
{
"in": "query",
"name": "filter",
"schema": {
"type": "string"
},
"required": true
}
]
}
}
}
}
```

### Schema Assumptions

`one-schema` provides a set of JSONSchema assumptions to help simplify Request/Response JSONSchema entries in commonly desired ways.

These assumptions are described by the `SchemaAssumptions` type in [`src/meta-schema.ts`](src/meta-schema.ts) and can be individually or wholly disabled in the Node API and at the command line via the `--asssumptions` flag.
2 changes: 1 addition & 1 deletion src/integration.koa.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ test('GET method', async () => {
{
implementation: {
'GET /posts': (ctx) => {
return ctx.request.body;
return ctx.request.query;
},
},
},
Expand Down
42 changes: 30 additions & 12 deletions src/koa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ import type { ParameterizedContext } from 'koa';
import type Router = require('koa-router');
import type { EndpointsOf, OneSchema } from './types';

// This declare is required to override the "declare" that comes from
// koa-bodyparser. Without this, the typings from one-schema will be
// overriden and collapsed into "any".
declare module 'koa' {
interface Request {
body?: unknown;
}
}

export type ImplementationOf<Schema extends OneSchema<any>, State, Context> = {
[Name in keyof EndpointsOf<Schema>]: (
context: ParameterizedContext<
State,
Context & {
params: EndpointsOf<Schema>[Name]['PathParams'];
request: {
body: EndpointsOf<Schema>[Name]['Request'];
};
request: Name extends `${'GET' | 'DELETE'} ${string}`
? { query: EndpointsOf<Schema>[Name]['Request'] }
: { body: EndpointsOf<Schema>[Name]['Request'] };
}
>,
) => Promise<EndpointsOf<Schema>[Name]['Response']>;
Expand Down Expand Up @@ -82,15 +91,24 @@ export const implementSchema = <State, Context, Schema extends OneSchema<any>>(
// 1. Validate the input data.
const requestSchema = Endpoints[endpoint].Request;
if (requestSchema) {
ctx.request.body = parse(
ctx,
endpoint,
{ ...requestSchema, definitions: Resources },
// 1a. For GET and DELETE, use the query parameters. Otherwise, use the body.
['GET', 'DELETE'].includes(method)
? ctx.request.query
: ctx.request.body,
);
// 1a. For GET and DELETE, validate the query params.
if (['GET', 'DELETE'].includes(method)) {
// @ts-ignore
ctx.request.query = parse(
ctx,
endpoint,
{ ...requestSchema, definitions: Resources },
ctx.request.query,
);
} else {
// 1b. Otherwise, use the body.
ctx.request.body = parse(
ctx,
endpoint,
{ ...requestSchema, definitions: Resources },
ctx.request.body,
);
}
}

// 2. Run the provided route handler.
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"inlineSources": false,
"inlineSourceMap": false,
"esModuleInterop": false,
"declaration": true
"declaration": true,
"skipLibCheck": true
}
}

0 comments on commit 2dd53d4

Please sign in to comment.