Skip to content

Commit 670735f

Browse files
author
Forbes Lindesay
authored
feat: do not require koa as dependency (#13)
This lets you publish libraries that validate koa requests, but don't depend on koa.
1 parent 01c4207 commit 670735f

File tree

7 files changed

+311
-275
lines changed

7 files changed

+311
-275
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"test": "jest --no-cache",
1717
"test:coverage": "yarn test --coverage",
1818
"test:watch": "yarn test --watch",
19+
"posttest": "tsc --noEmit",
1920
"clean": "rimraf lib && rimraf src/Example.validator.ts",
2021
"prebuild": "yarn clean",
2122
"build": "tsc",
@@ -68,7 +69,7 @@
6869
"json-stable-stringify": "^1.0.1",
6970
"minimatch": "^3.0.4",
7071
"tsconfig": "^7.0.0",
71-
"typescript-json-schema": "^0.38.0",
72+
"typescript-json-schema": "^0.38.3",
7273
"yargs": "^13.2.4"
7374
}
7475
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {Context} from 'koa';
2+
import {validateKoaRequest, RequestA} from './ComplexExample.validator';
3+
4+
declare const x: Context;
5+
export const y: RequestA = validateKoaRequest('RequestA')(x);

src/__tests__/output/ComplexExample.valiator.ts renamed to src/__tests__/output/ComplexExample.validator.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import Ajv = require('ajv');
2-
import {Context} from 'koa';
3-
import {inspect} from 'util';
42
import {MyEnum, TypeA, TypeB, RequestA, RequestB} from '../../ComplexExample';
3+
import {inspect} from 'util';
4+
export interface KoaContext {
5+
readonly request?: unknown; // {body?: unknown}
6+
readonly params?: unknown;
7+
readonly query?: unknown;
8+
throw(status: 400, message: string): unknown;
9+
}
510
export const ajv = new Ajv({allErrors: true, coerceTypes: false});
611

712
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
@@ -75,7 +80,7 @@ ajv.addSchema(Schema, 'Schema');
7580
export function validateKoaRequest(
7681
typeName: 'RequestA',
7782
): (
78-
ctx: Context,
83+
ctx: KoaContext,
7984
) => {
8085
params: RequestA['params'];
8186
query: RequestA['query'];
@@ -84,7 +89,7 @@ export function validateKoaRequest(
8489
export function validateKoaRequest(
8590
typeName: 'RequestB',
8691
): (
87-
ctx: Context,
92+
ctx: KoaContext,
8893
) => {
8994
params: unknown;
9095
query: RequestB['query'];
@@ -93,7 +98,7 @@ export function validateKoaRequest(
9398
export function validateKoaRequest(
9499
typeName: string,
95100
): (
96-
ctx: Context,
101+
ctx: KoaContext,
97102
) => {
98103
params: unknown;
99104
query: unknown;
@@ -102,7 +107,7 @@ export function validateKoaRequest(
102107
export function validateKoaRequest(
103108
typeName: string,
104109
): (
105-
ctx: Context,
110+
ctx: KoaContext,
106111
) => {
107112
params: any;
108113
query: any;
@@ -118,7 +123,7 @@ export function validateKoaRequest(
118123
const validateProperty = (
119124
prop: string,
120125
validator: any,
121-
ctx: Context,
126+
ctx: KoaContext,
122127
): any => {
123128
const data =
124129
prop === 'body'
@@ -133,7 +138,11 @@ export function validateKoaRequest(
133138
'Invalid request: ' +
134139
ajv.errorsText(validator.errors, {dataVar: prop}) +
135140
'\n\n' +
136-
inspect({params: ctx.params, query: ctx.query, body: ctx.body}),
141+
inspect({
142+
params: ctx.params,
143+
query: ctx.query,
144+
body: ctx.request && (ctx.request as any).body,
145+
}),
137146
);
138147
}
139148
}

src/__tests__/printValidator.test.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,29 @@ import {printTypeCollectionValidator} from '../printValidator';
33
import {writeFileSync} from 'fs';
44
import prettierFile from '../prettierFile';
55

6-
let validate: typeof import('./output/ComplexExample.valiator') = undefined as any;
6+
let validate: typeof import('./output/ComplexExample.validator') = undefined as any;
77

88
test('print', () => {
99
const {symbols, schema} = parse([
1010
__dirname + '/../ComplexExample.ts',
1111
]).getAllTypes();
1212
writeFileSync(
13-
__dirname + '/output/ComplexExample.valiator.ts',
13+
__dirname + '/output/ComplexExample.validator.ts',
1414
printTypeCollectionValidator(symbols, schema, '../../ComplexExample'),
1515
);
16-
prettierFile(__dirname + '/output/ComplexExample.valiator.ts');
17-
validate = require('./output/ComplexExample.valiator');
16+
prettierFile(__dirname + '/output/ComplexExample.validator.ts');
17+
writeFileSync(
18+
__dirname + '/output/ComplexExample.usage.ts',
19+
`
20+
import {Context} from 'koa';
21+
import {validateKoaRequest, RequestA} from './ComplexExample.validator';
22+
23+
declare const x: Context;
24+
export const y: RequestA = validateKoaRequest('RequestA')(x);
25+
`,
26+
);
27+
prettierFile(__dirname + '/output/ComplexExample.usage.ts');
28+
validate = require('./output/ComplexExample.validator');
1829
});
1930
test('validateValue', () => {
2031
expect(validate.validate('MyEnum')(0)).toBe(0);

src/printValidator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export function printTypeCollectionValidator(
2424
});
2525
return [
2626
t.IMPORT_AJV,
27-
...(koaTypes.length ? [t.IMPORT_KOA_CONTEXT, t.IMPORT_INSPECT] : []),
2827
t.importNamedTypes(symbols, relativePath),
28+
...(koaTypes.length ? [t.IMPORT_INSPECT, t.DECLARE_KOA_CONTEXT] : []),
2929
t.declareAJV(options),
3030
t.exportNamed(symbols),
3131
t.declareSchema('Schema', schema),

src/template.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ export type GENERATED_COMMENT = `// generated by typescript-json-validator`;
66

77
export const IMPORT_INSPECT = `import {inspect} from 'util';`;
88
export const IMPORT_AJV = `import Ajv = require('ajv');`;
9-
export const IMPORT_KOA_CONTEXT = `import {Context} from 'koa';`;
9+
10+
export const DECLARE_KOA_CONTEXT = `export interface KoaContext {
11+
readonly request?: unknown; // {body?: unknown}
12+
readonly params?: unknown;
13+
readonly query?: unknown;
14+
throw(status: 400, message: string): unknown;
15+
}`;
1016

1117
export const importNamedTypes = (names: string[], relativePath: string) =>
1218
`import {${names.join(', ')}} from '${relativePath}';`;
@@ -78,19 +84,19 @@ export const validateKoaRequestOverload = (
7884
typeName: string,
7985
schema: TJS.Definition,
8086
) =>
81-
`export function validateKoaRequest(typeName: '${typeName}'): (ctx: Context) => {
87+
`export function validateKoaRequest(typeName: '${typeName}'): (ctx: KoaContext) => {
8288
params: ${typeOf(typeName, 'params', schema)},
8389
query: ${typeOf(typeName, 'query', schema)},
8490
body: ${typeOf(typeName, 'body', schema)},
8591
};`;
8692

87-
export const VALIDATE_KOA_REQUEST_FALLBACK = `export function validateKoaRequest(typeName: string): (ctx: Context) => {
93+
export const VALIDATE_KOA_REQUEST_FALLBACK = `export function validateKoaRequest(typeName: string): (ctx: KoaContext) => {
8894
params: unknown,
8995
query: unknown,
9096
body: unknown,
9197
};`;
9298

93-
export const VALIDATE_KOA_REQUEST_IMPLEMENTATION = `export function validateKoaRequest(typeName: string): (ctx: Context) => {
99+
export const VALIDATE_KOA_REQUEST_IMPLEMENTATION = `export function validateKoaRequest(typeName: string): (ctx: KoaContext) => {
94100
params: any,
95101
query: any,
96102
body: any,
@@ -101,7 +107,7 @@ export const VALIDATE_KOA_REQUEST_IMPLEMENTATION = `export function validateKoaR
101107
const validateProperty = (
102108
prop: string,
103109
validator: any,
104-
ctx: Context,
110+
ctx: KoaContext,
105111
): any => {
106112
const data = prop === 'body' ? ctx.request && (ctx.request as any).body : (ctx as any)[prop];
107113
if (validator) {
@@ -110,7 +116,7 @@ export const VALIDATE_KOA_REQUEST_IMPLEMENTATION = `export function validateKoaR
110116
if (!valid) {
111117
ctx.throw(
112118
400,
113-
'Invalid request: ' + ajv.errorsText(validator.errors, {dataVar: prop}) + '\\n\\n' + inspect({params: ctx.params, query: ctx.query, body: ctx.body}),
119+
'Invalid request: ' + ajv.errorsText(validator.errors, {dataVar: prop}) + '\\n\\n' + inspect({params: ctx.params, query: ctx.query, body: ctx.request && (ctx.request as any).body}),
114120
);
115121
}
116122
}

0 commit comments

Comments
 (0)