@rosen-bridge/fastify-enhanced 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/dataTypes.d.ts +206 -428
- package/dist/dataTypes.d.ts.map +1 -1
- package/dist/dataTypes.js +3 -4
- package/dist/error.d.ts +5 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +9 -0
- package/dist/fastify.d.ts +8 -38
- package/dist/fastify.d.ts.map +1 -1
- package/dist/fastify.js +19 -153
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/jsonParser.d.ts +4 -0
- package/dist/jsonParser.d.ts.map +1 -0
- package/dist/jsonParser.js +18 -0
- package/dist/serializerCompiler.d.ts +14 -0
- package/dist/serializerCompiler.d.ts.map +1 -0
- package/dist/serializerCompiler.js +27 -0
- package/dist/swagger.d.ts +6 -0
- package/dist/swagger.d.ts.map +1 -0
- package/dist/swagger.js +42 -0
- package/dist/types.d.ts +8 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +4 -0
- package/lib/error.ts +9 -0
- package/lib/fastify.ts +22 -166
- package/lib/index.ts +2 -2
- package/lib/jsonParser.ts +23 -0
- package/lib/serializerCompiler.ts +42 -0
- package/lib/swagger.ts +52 -0
- package/lib/types.ts +3 -3
- package/lib/utils.ts +6 -0
- package/package.json +18 -19
- package/tests/bin.ts +13 -0
- package/tests/e2e.spec.ts +74 -0
- package/tests/mockRoute.ts +104 -0
- package/tests/testData.ts +101 -0
- package/tsconfig.build.json +2 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +2 -1
- package/vitest.config.ts +9 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/lib/dataTypes.ts +0 -7
package/lib/fastify.ts
CHANGED
|
@@ -1,41 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import Fastify, { FastifyBaseLogger, FastifyInstance } from 'fastify';
|
|
1
|
+
import 'zod-openapi/extend';
|
|
2
|
+
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
type FastifyZodOpenApiTypeProvider,
|
|
5
|
+
fastifyZodOpenApiPlugin,
|
|
7
6
|
validatorCompiler,
|
|
8
|
-
} from 'fastify-
|
|
9
|
-
import
|
|
10
|
-
import * as http from 'http';
|
|
7
|
+
} from 'fastify-zod-openapi';
|
|
8
|
+
import Fastify from 'fastify';
|
|
11
9
|
import JsonBigIntFactory from 'json-bigint';
|
|
12
|
-
|
|
10
|
+
|
|
13
11
|
import { FastifyWithZod, SwaggerOpts } from './types';
|
|
12
|
+
import { registerSwagger } from './swagger';
|
|
13
|
+
import { makeJsonParser } from './jsonParser';
|
|
14
|
+
import { makeSerializerCompiler } from './serializerCompiler';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* creates an instance of Fastify with Zod validation library as validator and
|
|
17
18
|
* type provider
|
|
18
|
-
*
|
|
19
|
-
* @param {string} [swaggerPath='/swagger']
|
|
20
|
-
* @param {boolean} [jsonHandler=JsonBigIntFactory({
|
|
21
|
-
* alwaysParseAsBig: false,
|
|
22
|
-
* useNativeBigInt: true,
|
|
23
|
-
* })]
|
|
24
|
-
* @param {*} [opts={ logger: true }]
|
|
25
|
-
* @return {Promise<
|
|
26
|
-
* FastifyInstance<
|
|
27
|
-
* http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
|
28
|
-
* http.IncomingMessage,
|
|
29
|
-
* http.ServerResponse<http.IncomingMessage>,
|
|
30
|
-
* FastifyBaseLogger,
|
|
31
|
-
* ZodTypeProvider
|
|
32
|
-
* >
|
|
33
|
-
* >}
|
|
34
19
|
*/
|
|
35
|
-
export const
|
|
20
|
+
export const makeFastify = async (
|
|
36
21
|
swaggerOpts: SwaggerOpts = {
|
|
37
22
|
path: '/swagger',
|
|
38
|
-
title: '',
|
|
23
|
+
title: 'api',
|
|
39
24
|
description: '',
|
|
40
25
|
version: '0.0.1',
|
|
41
26
|
},
|
|
@@ -43,155 +28,26 @@ export const createFastify = async (
|
|
|
43
28
|
jsonHandler = JsonBigIntFactory({
|
|
44
29
|
alwaysParseAsBig: false,
|
|
45
30
|
useNativeBigInt: true,
|
|
31
|
+
storeAsString: true,
|
|
46
32
|
})
|
|
47
33
|
): Promise<FastifyWithZod> => {
|
|
48
|
-
const fastify =
|
|
34
|
+
const fastify =
|
|
35
|
+
Fastify(opts).withTypeProvider<FastifyZodOpenApiTypeProvider>();
|
|
36
|
+
|
|
49
37
|
fastify.setValidatorCompiler(validatorCompiler);
|
|
50
38
|
|
|
51
|
-
const serializerCompiler =
|
|
39
|
+
const serializerCompiler = makeSerializerCompiler(jsonHandler);
|
|
52
40
|
fastify.setSerializerCompiler(serializerCompiler);
|
|
53
41
|
|
|
42
|
+
await fastify.register(fastifyZodOpenApiPlugin);
|
|
43
|
+
|
|
54
44
|
fastify.addContentTypeParser<string>(
|
|
55
45
|
'application/json',
|
|
56
46
|
{ parseAs: 'string' },
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const json = jsonHandler.parse(body);
|
|
60
|
-
done(null, json);
|
|
61
|
-
} catch (err) {
|
|
62
|
-
if (err instanceof Error || err === null) {
|
|
63
|
-
done(err, undefined);
|
|
64
|
-
} else {
|
|
65
|
-
const wrappedErr = new Error(
|
|
66
|
-
`An unexpected error occurred while trying to parse request body`,
|
|
67
|
-
{ cause: err }
|
|
68
|
-
);
|
|
69
|
-
done(wrappedErr, undefined);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
47
|
+
makeJsonParser(jsonHandler)
|
|
73
48
|
);
|
|
74
49
|
|
|
75
|
-
await
|
|
76
|
-
return fastify;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* creates a Fastify serializer compiler based on Zod validation library and
|
|
81
|
-
* using the passed json parser/serializer
|
|
82
|
-
*
|
|
83
|
-
* @param {typeof JSON} jsonHandler
|
|
84
|
-
* @return {(FastifySerializerCompiler<
|
|
85
|
-
* | ZodAny
|
|
86
|
-
* | {
|
|
87
|
-
* properties: ZodAny;
|
|
88
|
-
* }
|
|
89
|
-
* >)}
|
|
90
|
-
*/
|
|
91
|
-
const getSerializerCompiler = (
|
|
92
|
-
jsonHandler: ReturnType<typeof JsonBigIntFactory>
|
|
93
|
-
): FastifySerializerCompiler<
|
|
94
|
-
| ZodAny
|
|
95
|
-
| {
|
|
96
|
-
properties: ZodAny;
|
|
97
|
-
}
|
|
98
|
-
> => {
|
|
99
|
-
const hasOwnProperty = <T, K extends PropertyKey>(
|
|
100
|
-
obj: T,
|
|
101
|
-
prop: K
|
|
102
|
-
): obj is T & Record<K, any> => {
|
|
103
|
-
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* resolve the right schema to be used
|
|
108
|
-
*
|
|
109
|
-
* @param {(ZodAny | { properties: ZodAny })} maybeSchema
|
|
110
|
-
* @return {Pick<ZodAny, 'safeParse'>}
|
|
111
|
-
*/
|
|
112
|
-
function resolveSchema(
|
|
113
|
-
maybeSchema: ZodAny | { properties: ZodAny }
|
|
114
|
-
): Pick<ZodAny, 'safeParse'> {
|
|
115
|
-
if (hasOwnProperty(maybeSchema, 'safeParse')) {
|
|
116
|
-
return maybeSchema;
|
|
117
|
-
}
|
|
118
|
-
if (hasOwnProperty(maybeSchema, 'properties')) {
|
|
119
|
-
return maybeSchema.properties;
|
|
120
|
-
}
|
|
121
|
-
throw new Error(`Invalid schema passed: ${JSON.stringify(maybeSchema)}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
class ResponseValidationError extends Error {
|
|
125
|
-
public details: Record<string, any>;
|
|
50
|
+
await registerSwagger(fastify, swaggerOpts);
|
|
126
51
|
|
|
127
|
-
|
|
128
|
-
super("Response doesn't match the schema");
|
|
129
|
-
this.name = 'ResponseValidationError';
|
|
130
|
-
this.details = validationResult.error;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* creates a serializer compiler based on Zod library
|
|
136
|
-
*
|
|
137
|
-
* @param { schema: maybeSchema }
|
|
138
|
-
*/
|
|
139
|
-
const serializerCompiler: FastifySerializerCompiler<
|
|
140
|
-
ZodAny | { properties: ZodAny }
|
|
141
|
-
> =
|
|
142
|
-
({ schema: maybeSchema }) =>
|
|
143
|
-
(data) => {
|
|
144
|
-
const schema: Pick<ZodAny, 'safeParse'> = resolveSchema(maybeSchema);
|
|
145
|
-
|
|
146
|
-
const result = schema.safeParse(data);
|
|
147
|
-
if (result.success) {
|
|
148
|
-
return jsonHandler.stringify(result.data);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
throw new ResponseValidationError(result);
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
return serializerCompiler;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* adds swagger to the fastify instance
|
|
159
|
-
*
|
|
160
|
-
* @param {Awaited<ReturnType<typeof createFastify>>} fastify
|
|
161
|
-
* @param {string} path
|
|
162
|
-
*/
|
|
163
|
-
const addSwaggerRoute = async (fastify: FastifyWithZod, opts: SwaggerOpts) => {
|
|
164
|
-
fastify.register(swagger, {
|
|
165
|
-
openapi: {
|
|
166
|
-
info: {
|
|
167
|
-
title: opts.title,
|
|
168
|
-
description: opts.description,
|
|
169
|
-
version: opts.version,
|
|
170
|
-
},
|
|
171
|
-
servers: [],
|
|
172
|
-
},
|
|
173
|
-
transform: jsonSchemaTransform,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
fastify.register(swaggerUi, {
|
|
177
|
-
routePrefix: opts.path,
|
|
178
|
-
uiConfig: {
|
|
179
|
-
docExpansion: 'full',
|
|
180
|
-
deepLinking: false,
|
|
181
|
-
},
|
|
182
|
-
uiHooks: {
|
|
183
|
-
onRequest: function (request: any, reply: any, next: () => void) {
|
|
184
|
-
next();
|
|
185
|
-
},
|
|
186
|
-
preHandler: function (request: any, reply: any, next: () => void) {
|
|
187
|
-
next();
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
staticCSP: true,
|
|
191
|
-
transformStaticCSP: (header: any) => header,
|
|
192
|
-
transformSpecification: (swaggerObject: any, request: any, reply: any) => {
|
|
193
|
-
return swaggerObject;
|
|
194
|
-
},
|
|
195
|
-
transformSpecificationClone: true,
|
|
196
|
-
});
|
|
52
|
+
return fastify;
|
|
197
53
|
};
|
package/lib/index.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ContentTypeParserDoneFunction } from 'fastify/types/content-type-parser';
|
|
2
|
+
import JsonBigIntFactory from 'json-bigint';
|
|
3
|
+
|
|
4
|
+
export const makeJsonParser = (
|
|
5
|
+
jsonHandler: ReturnType<typeof JsonBigIntFactory>
|
|
6
|
+
) => {
|
|
7
|
+
return (req: unknown, body: string, done: ContentTypeParserDoneFunction) => {
|
|
8
|
+
try {
|
|
9
|
+
const json = jsonHandler.parse(body);
|
|
10
|
+
done(null, json);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
if (err instanceof Error || err === null) {
|
|
13
|
+
done(err, undefined);
|
|
14
|
+
} else {
|
|
15
|
+
const wrappedErr = new Error(
|
|
16
|
+
`An unexpected error occurred while trying to parse request body`,
|
|
17
|
+
{ cause: err }
|
|
18
|
+
);
|
|
19
|
+
done(wrappedErr, undefined);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FastifySerializerCompiler } from 'fastify/types/schema';
|
|
2
|
+
import { ZodAny } from 'zod';
|
|
3
|
+
import JsonBigIntFactory from 'json-bigint';
|
|
4
|
+
|
|
5
|
+
import { ResponseValidationError } from './error';
|
|
6
|
+
import { hasOwnProperty } from './utils';
|
|
7
|
+
|
|
8
|
+
type SerType = FastifySerializerCompiler<ZodAny | { properties: ZodAny }>;
|
|
9
|
+
type JsonHandler = ReturnType<typeof JsonBigIntFactory>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* creates a Fastify serializer compiler based on Zod validation library and
|
|
13
|
+
* using the passed json parser/serializer
|
|
14
|
+
*/
|
|
15
|
+
export const makeSerializerCompiler =
|
|
16
|
+
(jsonHandler: JsonHandler): SerType =>
|
|
17
|
+
(schemaDef) =>
|
|
18
|
+
(data) => {
|
|
19
|
+
const schema: Pick<ZodAny, 'safeParse'> = resolveSchema(schemaDef.schema);
|
|
20
|
+
|
|
21
|
+
const result = schema.safeParse(data);
|
|
22
|
+
if (result.success) {
|
|
23
|
+
return jsonHandler.stringify(result.data);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
throw new ResponseValidationError(result);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* resolve the right schema to be used
|
|
31
|
+
*/
|
|
32
|
+
const resolveSchema = (
|
|
33
|
+
maybeSchema: ZodAny | { properties: ZodAny }
|
|
34
|
+
): Pick<ZodAny, 'safeParse'> => {
|
|
35
|
+
if (hasOwnProperty(maybeSchema, 'safeParse')) {
|
|
36
|
+
return maybeSchema;
|
|
37
|
+
}
|
|
38
|
+
if (hasOwnProperty(maybeSchema, 'properties')) {
|
|
39
|
+
return maybeSchema.properties;
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`Invalid schema passed: ${JSON.stringify(maybeSchema)}`);
|
|
42
|
+
};
|
package/lib/swagger.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import swagger from '@fastify/swagger';
|
|
2
|
+
import swaggerUi from '@fastify/swagger-ui';
|
|
3
|
+
import {
|
|
4
|
+
fastifyZodOpenApiTransform,
|
|
5
|
+
fastifyZodOpenApiTransformObject,
|
|
6
|
+
} from 'fastify-zod-openapi';
|
|
7
|
+
import { ZodOpenApiVersion } from 'zod-openapi';
|
|
8
|
+
|
|
9
|
+
import { FastifyWithZod, SwaggerOpts } from './types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* adds swagger to the fastify instance
|
|
13
|
+
*/
|
|
14
|
+
export const registerSwagger = async (
|
|
15
|
+
fastify: FastifyWithZod,
|
|
16
|
+
opts: SwaggerOpts
|
|
17
|
+
) => {
|
|
18
|
+
await fastify.register(swagger, {
|
|
19
|
+
openapi: {
|
|
20
|
+
openapi: '3.1.0' as ZodOpenApiVersion,
|
|
21
|
+
info: {
|
|
22
|
+
title: opts.title,
|
|
23
|
+
description: opts.description,
|
|
24
|
+
version: opts.version,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
transform: fastifyZodOpenApiTransform,
|
|
28
|
+
transformObject: fastifyZodOpenApiTransformObject,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await fastify.register(swaggerUi, {
|
|
32
|
+
routePrefix: opts.path,
|
|
33
|
+
uiConfig: {
|
|
34
|
+
docExpansion: 'full',
|
|
35
|
+
deepLinking: false,
|
|
36
|
+
},
|
|
37
|
+
uiHooks: {
|
|
38
|
+
onRequest: (request: any, reply: any, next: () => void) => {
|
|
39
|
+
next();
|
|
40
|
+
},
|
|
41
|
+
preHandler: (request: any, reply: any, next: () => void) => {
|
|
42
|
+
next();
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
staticCSP: true,
|
|
46
|
+
transformStaticCSP: (header: any) => header,
|
|
47
|
+
transformSpecification: (swaggerObject: any, request: any, reply: any) => {
|
|
48
|
+
return swaggerObject;
|
|
49
|
+
},
|
|
50
|
+
transformSpecificationClone: true,
|
|
51
|
+
});
|
|
52
|
+
};
|
package/lib/types.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { FastifyBaseLogger, FastifyInstance } from 'fastify';
|
|
2
|
-
import { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
3
1
|
import * as http from 'http';
|
|
2
|
+
import { FastifyBaseLogger, FastifyInstance } from 'fastify';
|
|
3
|
+
import type { FastifyZodOpenApiTypeProvider } from 'fastify-zod-openapi';
|
|
4
4
|
|
|
5
5
|
export type FastifyWithZod = FastifyInstance<
|
|
6
6
|
http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
|
7
7
|
http.IncomingMessage,
|
|
8
8
|
http.ServerResponse<http.IncomingMessage>,
|
|
9
9
|
FastifyBaseLogger,
|
|
10
|
-
|
|
10
|
+
FastifyZodOpenApiTypeProvider
|
|
11
11
|
>;
|
|
12
12
|
|
|
13
13
|
export interface SwaggerOpts {
|
package/lib/utils.ts
ADDED
package/package.json
CHANGED
|
@@ -1,42 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rosen-bridge/fastify-enhanced",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "a wrapper around fastify web framework to make it even better",
|
|
5
5
|
"repository": "git+https://github.com/rosen-bridge/utils.git",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "MIT",
|
|
7
7
|
"author": "Rosen Team",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc --build tsconfig.build.json",
|
|
13
|
-
"coverage": "npm run test -- --coverage",
|
|
13
|
+
"coverage": "npm run test -- --run --coverage",
|
|
14
14
|
"lint": "eslint --fix . && npm run prettify",
|
|
15
15
|
"prettify": "prettier --write . --ignore-path ./.gitignore",
|
|
16
16
|
"release": "npm run build && npm publish --access public",
|
|
17
|
-
"test": "NODE_OPTIONS
|
|
17
|
+
"test": "NODE_OPTIONS='--import tsx' vitest",
|
|
18
|
+
"test:bin": "tsx tests/bin.ts",
|
|
18
19
|
"type-check": "tsc --noEmit"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^
|
|
22
|
-
"@
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"prettier": "2.7.1",
|
|
28
|
-
"typescript": "^5.0.0",
|
|
29
|
-
"vitest": "^0.26.2"
|
|
22
|
+
"@types/node": "^20.11.0",
|
|
23
|
+
"@vitest/coverage-istanbul": "^2.0.5",
|
|
24
|
+
"tsx": "^4.19.2",
|
|
25
|
+
"typescript": "^5.8.3",
|
|
26
|
+
"vitest": "^2.0.5",
|
|
27
|
+
"@types/json-bigint": "^1.0.1"
|
|
30
28
|
},
|
|
31
29
|
"engines": {
|
|
32
|
-
"node": ">=
|
|
30
|
+
"node": ">=20.11.0"
|
|
33
31
|
},
|
|
34
32
|
"dependencies": {
|
|
35
|
-
"@fastify/swagger": "^
|
|
36
|
-
"@fastify/swagger-ui": "^
|
|
37
|
-
"fastify": "^
|
|
38
|
-
"fastify-
|
|
33
|
+
"@fastify/swagger": "^9.5.1",
|
|
34
|
+
"@fastify/swagger-ui": "^5.2.3",
|
|
35
|
+
"fastify": "^5.3.3",
|
|
36
|
+
"fastify-zod-openapi": "^4.1.2",
|
|
39
37
|
"json-bigint": "^1.0.0",
|
|
40
|
-
"zod": "^3.
|
|
38
|
+
"zod": "^3.25.56",
|
|
39
|
+
"zod-openapi": "^4.2.4"
|
|
41
40
|
}
|
|
42
41
|
}
|
package/tests/bin.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// for manual testing
|
|
2
|
+
|
|
3
|
+
import { makeFastify } from '../lib/fastify';
|
|
4
|
+
import { FastifyWithZod } from '../lib/types';
|
|
5
|
+
import { mockRoutes } from './mockRoute';
|
|
6
|
+
|
|
7
|
+
const host = '127.0.0.1';
|
|
8
|
+
const port = 1338;
|
|
9
|
+
const mockServer: FastifyWithZod = await makeFastify();
|
|
10
|
+
mockServer.register(async (s: FastifyWithZod) => mockRoutes(s));
|
|
11
|
+
await mockServer.ready();
|
|
12
|
+
await mockServer.listen({ host, port });
|
|
13
|
+
console.log(`api service started at http://${host}:${port}`);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { FastifyWithZod, makeFastify } from '../lib';
|
|
2
|
+
import { mockRoutes } from './mockRoute';
|
|
3
|
+
import { apiSpec } from './testData';
|
|
4
|
+
|
|
5
|
+
describe('e2e', () => {
|
|
6
|
+
let mockServer: FastifyWithZod;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
mockServer = await makeFastify();
|
|
10
|
+
await mockServer.register(async () => mockRoutes(mockServer));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await mockServer.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @target fastifyServer[POST /mock] should respond with string representation of the bigint fields in the request body
|
|
19
|
+
* @dependencies
|
|
20
|
+
* @scenario
|
|
21
|
+
* - define a mock post route with bigint fields in its request body schema
|
|
22
|
+
* - send the request
|
|
23
|
+
* @expected
|
|
24
|
+
* - response status should have been 200
|
|
25
|
+
* - response should have matched the string representation of the same bigint fields as input
|
|
26
|
+
*/
|
|
27
|
+
it('should respond with string representation of the bigint fields in the request body', async () => {
|
|
28
|
+
// act
|
|
29
|
+
const result = await mockServer.inject({
|
|
30
|
+
method: 'POST',
|
|
31
|
+
url: '/mock',
|
|
32
|
+
body: {
|
|
33
|
+
num1: 10_000,
|
|
34
|
+
num2: '999',
|
|
35
|
+
num3: '10100',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// assert
|
|
40
|
+
expect(result.statusCode).toEqual(200);
|
|
41
|
+
expect(result.json()).toEqual({
|
|
42
|
+
array: [
|
|
43
|
+
{
|
|
44
|
+
num1: '10000',
|
|
45
|
+
num2: '999',
|
|
46
|
+
num3: '10100',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
sum: '21099',
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @target fastifyServer[POST /mock] should add routes to the swagger doc with support for bigint type
|
|
55
|
+
* @dependencies
|
|
56
|
+
* @scenario
|
|
57
|
+
* - define a mock post route with bigint fields in its request body schema
|
|
58
|
+
* - send a request to get openapi doc
|
|
59
|
+
* @expected
|
|
60
|
+
* - response status should have been 200
|
|
61
|
+
* - response should have matched the correct openapi doc with int64 as type of bigint fields
|
|
62
|
+
*/
|
|
63
|
+
it('should add routes to the swagger doc with support for bigint type', async () => {
|
|
64
|
+
// act
|
|
65
|
+
const result = await mockServer.inject({
|
|
66
|
+
method: 'GET',
|
|
67
|
+
url: '/swagger/json',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// assert
|
|
71
|
+
expect(result.statusCode).toEqual(200);
|
|
72
|
+
expect(result.body).toEqual(JSON.stringify(apiSpec));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { FastifyWithZod } from '../lib';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* two mock routes
|
|
7
|
+
* @param server
|
|
8
|
+
*/
|
|
9
|
+
export const mockRoutes = (server: FastifyWithZod) => {
|
|
10
|
+
server.get(
|
|
11
|
+
'/health',
|
|
12
|
+
{
|
|
13
|
+
schema: {
|
|
14
|
+
response: {
|
|
15
|
+
200: HealthResponseSchema,
|
|
16
|
+
500: ErrorResponseSchema,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
async (request, reply) => {
|
|
21
|
+
try {
|
|
22
|
+
reply.status(200).send({
|
|
23
|
+
message: 673n,
|
|
24
|
+
number: 673,
|
|
25
|
+
});
|
|
26
|
+
} catch (error: any) {
|
|
27
|
+
reply.status(500).send({ message: error.message });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
server.post(
|
|
33
|
+
'/mock',
|
|
34
|
+
{
|
|
35
|
+
schema: {
|
|
36
|
+
body: RequestSchema,
|
|
37
|
+
response: {
|
|
38
|
+
200: SuccessResponseSchema,
|
|
39
|
+
500: ErrorResponseSchema,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
async (request, reply) => {
|
|
44
|
+
try {
|
|
45
|
+
reply.status(200).send({
|
|
46
|
+
array: [
|
|
47
|
+
{
|
|
48
|
+
num1: request.body.num1.toString(),
|
|
49
|
+
num2: request.body.num2.toString(),
|
|
50
|
+
num3: request.body.num3?.toString(),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
sum: (
|
|
54
|
+
request.body.num1 +
|
|
55
|
+
request.body.num2 +
|
|
56
|
+
(request.body.num3 ?? 0n)
|
|
57
|
+
).toString(),
|
|
58
|
+
});
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
reply.status(500).send({ message: error.message });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const RequestSchema = z.object({
|
|
67
|
+
num1: z.coerce.bigint(),
|
|
68
|
+
num2: z.coerce.bigint(),
|
|
69
|
+
num3: z.coerce.bigint(),
|
|
70
|
+
num4: z.optional(z.coerce.bigint()),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const ResponseObjectSchema = z.object({
|
|
74
|
+
num1: z.string(),
|
|
75
|
+
num2: z.string(),
|
|
76
|
+
num3: z.string(),
|
|
77
|
+
num4: z.optional(z.string()),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const SuccessResponseSchema = z.object({
|
|
81
|
+
array: z.array(ResponseObjectSchema),
|
|
82
|
+
sum: z.string(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export const HealthResponseSchema = {
|
|
86
|
+
content: {
|
|
87
|
+
'application/json': {
|
|
88
|
+
schema: z.object({
|
|
89
|
+
message: z.bigint(),
|
|
90
|
+
number: z.number(),
|
|
91
|
+
}),
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const ErrorResponseSchema = {
|
|
97
|
+
content: {
|
|
98
|
+
'application/json': {
|
|
99
|
+
schema: z.object({
|
|
100
|
+
message: z.string(),
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|