From e15f27313361b333cc3dad517ded4168d7e36bf0 Mon Sep 17 00:00:00 2001 From: Ilia Baryshnikov Date: Thu, 26 Oct 2023 16:28:32 +0200 Subject: [PATCH 01/11] feat: allow jsonified variants in response --- src/typed-fastify.ts | 3 ++- test/fixtures.ts | 4 ++++ test/test_schema.ts | 12 ++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/typed-fastify.ts b/src/typed-fastify.ts index b7ddc0c..4438ff5 100644 --- a/src/typed-fastify.ts +++ b/src/typed-fastify.ts @@ -1,4 +1,5 @@ import type * as F from 'fastify'; +import type { Jsonify } from 'type-fest'; import { RouteGenericInterface } from 'fastify/types/route'; import { RequestRouteOptions } from 'fastify/types/request'; @@ -224,7 +225,7 @@ interface Reply< ] : [Get2] extends [never] ? [] - : [Get2] + : [Get2 | Jsonify>] ): AsReply; readonly request: Request; diff --git a/test/fixtures.ts b/test/fixtures.ts index 41b52dc..f0f31fe 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -28,6 +28,10 @@ export const defaultService: Service = { 'GET /user_and_obj': (req, reply) => { return reply.status(200).send([{ name: 'user1' }, { id: '1', type: 'TEST' }, { any: 'thing' }]); }, + 'POST /jsonify': (req, reply) => { + const { date } = req.body; + return reply.status(200).send({ date: date.toJSON() }); + }, 'POST /': (req, reply) => { if (req.operationPath !== 'POST /') { throw new Error('Should never happen'); diff --git a/test/test_schema.ts b/test/test_schema.ts index 4510120..e2bb18c 100644 --- a/test/test_schema.ts +++ b/test/test_schema.ts @@ -70,6 +70,18 @@ export interface TestSchema extends Schema { }; }; }; + 'POST /jsonify': { + request: { + body: { + date: Date; + }; + }; + response: { + 200: { + content: { date: Date }; + }; + }; + }; 'GET /empty': { response: { 204: {}; From a7dd1b9e8e488e24ee989e95d42fe20dcbc3d77f Mon Sep 17 00:00:00 2001 From: Ilia Baryshnikov Date: Thu, 26 Oct 2023 16:36:18 +0200 Subject: [PATCH 02/11] chore: update snapshot --- .../test/integration.test.ts.test.cjs | 80 ++++++++++++++++++- test/test_schema.gen.json | 38 ++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/tap-snapshots/test/integration.test.ts.test.cjs b/tap-snapshots/test/integration.test.ts.test.cjs index cf31d00..769c8ac 100644 --- a/tap-snapshots/test/integration.test.ts.test.cjs +++ b/tap-snapshots/test/integration.test.ts.test.cjs @@ -1230,7 +1230,7 @@ Object { "Headers": Array [ "HTTP/1.1 200 OK", "content-type: application/json; charset=utf-8", - "content-length: 3735", + "content-length: 4071", "Date: dateString", "Connection: keep-alive", ], @@ -1491,6 +1491,45 @@ Object { }, }, }, + "/jsonify": Object { + "post": Object { + "parameters": Array [ + Object { + "in": "body", + "name": "body", + "schema": Object { + "properties": Object { + "date": Object { + "format": "date-time", + "type": "string", + }, + }, + "required": Array [ + "date", + ], + "type": "object", + }, + }, + ], + "responses": Object { + "200": Object { + "description": "Default Response", + "schema": Object { + "properties": Object { + "date": Object { + "format": "date-time", + "type": "string", + }, + }, + "required": Array [ + "date", + ], + "type": "object", + }, + }, + }, + }, + }, "/matches": Object { "get": Object { "parameters": Array [ @@ -1888,6 +1927,45 @@ Object { }, }, }, + "/jsonify": Object { + "post": Object { + "parameters": Array [ + Object { + "in": "body", + "name": "body", + "schema": Object { + "properties": Object { + "date": Object { + "format": "date-time", + "type": "string", + }, + }, + "required": Array [ + "date", + ], + "type": "object", + }, + }, + ], + "responses": Object { + "200": Object { + "description": "Default Response", + "schema": Object { + "properties": Object { + "date": Object { + "format": "date-time", + "type": "string", + }, + }, + "required": Array [ + "date", + ], + "type": "object", + }, + }, + }, + }, + }, "/matches": Object { "get": Object { "parameters": Array [ diff --git a/test/test_schema.gen.json b/test/test_schema.gen.json index 55779fa..ba4d29d 100644 --- a/test/test_schema.gen.json +++ b/test/test_schema.gen.json @@ -230,6 +230,42 @@ } } }, + "POST /jsonify": { + "request": { + "type": "object", + "required": [ + "body" + ], + "properties": { + "body": { + "type": "object", + "required": [ + "date" + ], + "properties": { + "date": { + "type": "string", + "format": "date-time" + } + } + } + } + }, + "response": { + "200": { + "type": "object", + "required": [ + "date" + ], + "properties": { + "date": { + "type": "string", + "format": "date-time" + } + } + } + } + }, "GET /empty": { "request": {}, "response": {} @@ -396,5 +432,5 @@ } } }, - "$hash": "55f23702a95be2a6d63340bde05157e37feed81b4eeb0badfccc120a27d59da5__v1.2.0" + "$hash": "1252b062241bb72c9f02a27d8f3c6cee7f3bd8a1e7a8142a5a4b88038790d19f__v1.2.0" } \ No newline at end of file From 636b3435428728498a3cc93191861d3471a18eae Mon Sep 17 00:00:00 2001 From: Ilia Baryshnikov Date: Thu, 26 Oct 2023 19:03:31 +0200 Subject: [PATCH 03/11] fix!: jsonify req.body --- src/typed-fastify.ts | 2 +- test/fixtures.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/typed-fastify.ts b/src/typed-fastify.ts index 4438ff5..84e29ab 100644 --- a/src/typed-fastify.ts +++ b/src/typed-fastify.ts @@ -350,7 +350,7 @@ interface Request< readonly operationPath: Path; readonly method: ROptions['method']; // A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request. - readonly body: ROptions['method'] extends 'GET' ? never : Get; + readonly body: ROptions['method'] extends 'GET' ? never : Jsonify>; readonly routeOptions: Id>; readonly routerMethod: ROptions['method']; readonly headers: Get; diff --git a/test/fixtures.ts b/test/fixtures.ts index f0f31fe..30c5245 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -30,7 +30,8 @@ export const defaultService: Service = { }, 'POST /jsonify': (req, reply) => { const { date } = req.body; - return reply.status(200).send({ date: date.toJSON() }); + date.charAt; // ok for string, not ok for Date + return reply.status(200).send({ date: new Date(date).toJSON() }); }, 'POST /': (req, reply) => { if (req.operationPath !== 'POST /') { From f27e6f8e4ebe9a71ce04f9e91c36337b306824a1 Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Sun, 29 Oct 2023 06:28:34 +0200 Subject: [PATCH 04/11] feat!: Better support for json-like input and outputs in the schema --- generator/gen.ts | 5 ++ package.json | 4 +- pnpm-lock.yaml | 27 ++++++-- src/schema.ts | 20 +++++- src/type-utils.ts | 60 +++++++++++++++++ src/typed-fastify.ts | 17 ++--- .../test/integration.test.ts.test.cjs | 49 ++++++++++++-- test/fixtures.ts | 11 ++-- test/integration.test.ts | 17 ++++- test/test_schema.gen.json | 64 ++++++++++++++----- test/test_schema.ts | 12 +++- test/typed-fastify.test-d.ts | 50 +++++++++++++++ 12 files changed, 288 insertions(+), 48 deletions(-) create mode 100644 src/type-utils.ts diff --git a/generator/gen.ts b/generator/gen.ts index 83fb7e5..2e06adf 100755 --- a/generator/gen.ts +++ b/generator/gen.ts @@ -124,15 +124,20 @@ export default async (params: { files: string[] }) => { }; const PLACEHOLDER_ID = '@__PLACEHOLDER_ID__@' + Date.now(); + const defaultAgs = TJS.getDefaultArgs(); + const settings: TJS.PartialArgs = { required: true, ref: true, + noExtraProps: true, aliasRef: false, skipLibCheck: true, topRef: true, ignoreErrors: true, strictNullChecks: true, id: PLACEHOLDER_ID, + // add support for ajv-keywords + validationKeywords: [...defaultAgs.validationKeywords, 'instanceof', 'typeof'], }; let { files } = params; diff --git a/package.json b/package.json index 321f4ae..bc563d0 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "glob": "^10.3.4", "json-schema-merge-allof": "^0.8.1", "json-schema-traverse": "^1.0.0", + "type-fest": "^4.6.0", "typescript-json-schema": "^0.61.0", "yargs": "^17.7.2" }, @@ -26,6 +27,8 @@ "@types/split2": "^4.2.0", "@types/tap": "^15.0.9", "@types/yargs": "^17.0.24", + "ajv-formats": "2.1.1", + "ajv-keywords": "5.1.0", "coveralls": "3.1.1", "fastify": "^4.23.2", "husky": "^8.0.3", @@ -38,7 +41,6 @@ "tap": "^16.3.8", "ts-node-dev": "^2.0.0", "tsd": "^0.29.0", - "type-fest": "^4.3.1", "typescript": "^5.2.2" }, "directories": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfe2fcc..dec562b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: json-schema-traverse: specifier: ^1.0.0 version: 1.0.0 + type-fest: + specifier: ^4.6.0 + version: 4.6.0 typescript-json-schema: specifier: ^0.61.0 version: 0.61.0 @@ -58,6 +61,12 @@ devDependencies: '@types/yargs': specifier: ^17.0.24 version: 17.0.24 + ajv-formats: + specifier: 2.1.1 + version: 2.1.1(ajv@8.12.0) + ajv-keywords: + specifier: 5.1.0 + version: 5.1.0(ajv@8.12.0) coveralls: specifier: 3.1.1 version: 3.1.1 @@ -94,9 +103,6 @@ devDependencies: tsd: specifier: ^0.29.0 version: 0.29.0 - type-fest: - specifier: ^4.3.1 - version: 4.3.1 typescript: specifier: ^5.2.2 version: 5.2.2 @@ -689,6 +695,15 @@ packages: ajv: 8.12.0 dev: true + /ajv-keywords@5.1.0(ajv@8.12.0): + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + dependencies: + ajv: 8.12.0 + fast-deep-equal: 3.1.3 + dev: true + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -3617,10 +3632,10 @@ packages: engines: {node: '>=10'} dev: true - /type-fest@4.3.1: - resolution: {integrity: sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==} + /type-fest@4.6.0: + resolution: {integrity: sha512-rLjWJzQFOq4xw7MgJrCZ6T1jIOvvYElXT12r+y0CC6u67hegDHaxcPqb2fZHOGlqxugGQPNB1EnTezjBetkwkw==} engines: {node: '>=16'} - dev: true + dev: false /typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} diff --git a/src/schema.ts b/src/schema.ts index 9a43385..9cf209c 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -6,6 +6,7 @@ import type { Stream } from 'stream'; export type StatusCode = | 100 | '100' // Continue | 101 | '101' // Switching Protocols + | 103 | '103' // Early Hints | 200 | '200' // OK | 201 | '201' // Created | 202 | '202' // Accepted @@ -13,6 +14,9 @@ export type StatusCode = | 204 | '204' // No Content | 205 | '205' // Reset Content | 206 | '206' // Partial Content + | 207 | '207' // Multi-Status + | 208 | '208' // Already Reported + | 226 | '226' // IM Used | 300 | '300' // Multiple Choices | 301 | '301' // Moved Permanently | 302 | '302' // Found @@ -20,6 +24,7 @@ export type StatusCode = | 304 | '304' // Not Modified | 305 | '305' // Use Proxy | 307 | '307' // Temporary Redirect + | 308 | '308' // Permanent Redirect | 400 | '400' // Bad Request | 401 | '401' // Unauthorized | 402 | '402' // Payment Required @@ -39,12 +44,22 @@ export type StatusCode = | 416 | '416' // Range Not Satisfiable | 417 | '417' // Expectation Failed | 426 | '426' // Upgrade Required + | 427 | '427' // Unassigned + | 428 | '428' // Precondition Required + | 429 | '429' // Too Many Requests + | 431 | '431' // Request Header Fields Too Large + | 451 | '451' // Unavailable For Legal Reasons | 500 | '500' // Internal Server Error | 501 | '501' // Not Implemented | 502 | '502' // Bad Gateway | 503 | '503' // Service Unavailable | 504 | '504' // Gateway Timeout - | 505 | '505'; // HTTP Version Not Supported; + | 505 | '505' // HTTP Version Not Supported; + | 506 | '506' // Variant Also Negotiates + | 507 | '507' // Insufficient Storage + | 508 | '508' // Loop Detected + | 510 | '510' // Not Extended + | 511 | '511' // Network Authentication Required export interface FastifyError extends Error { code: string; @@ -71,7 +86,8 @@ export interface Operation { }; }; } + export interface Schema { readonly __SCHEMA_TAG__?: 'BETTER-FASTIFY-SCHEMA'; - paths: Record<`${HTTPMethods} ${string}`, Partial>; + paths: Record<`${HTTPMethods} ${string}`, Operation>; } diff --git a/src/type-utils.ts b/src/type-utils.ts new file mode 100644 index 0000000..a946a1f --- /dev/null +++ b/src/type-utils.ts @@ -0,0 +1,60 @@ +import { PositiveInfinity, NegativeInfinity } from 'type-fest/source/numeric'; +import { JsonPrimitive, JsonValue } from 'type-fest/source/basic'; +import { EmptyObject } from 'type-fest/source/empty-object'; +import { TypedArray } from 'type-fest/source/typed-array'; +import { JsonifyList } from 'type-fest/source/jsonify'; +import { WritableDeep } from 'type-fest/source/writable-deep'; + +export type Id = T extends infer U ? { [K in keyof U]: U[K] } : never; +export type Get = P extends keyof T ? T[P] : never; +export type Get2 = Get, P2>; +export type IsEqual = (() => G extends T ? 1 : 2) extends () => G extends U ? 1 : 2 ? true : false; + +type IsNotJsonableError = Invalid<`${Extract} is not Json-like`> & {}; + +type NotJsonable = ((...arguments_: any[]) => any) | undefined | symbol | RegExp | Function; + +// tweaked version of Jsonify from type-fest +export type Jsonlike = T extends PositiveInfinity | NegativeInfinity + ? null + : T extends NotJsonable + ? IsNotJsonableError<'Passed value'> + : T extends JsonPrimitive + ? T + : // Any object with toJSON is special case + T extends { + toJSON(): infer J; + } + ? (() => J) extends () => JsonValue // Is J assignable to JsonValue? + ? DoNotCastToPrimitive extends true + ? T + : J // Then T is Jsonable and its Jsonable value is J + : Jsonlike // Maybe if we look a level deeper we'll find a JsonValue + : // Instanced primitives are objects + T extends Number + ? number + : T extends String + ? string + : T extends Boolean + ? boolean + : T extends Map | Set + ? EmptyObject + : T extends TypedArray + ? Record // Non-JSONable type union was found not empty + : T extends [] + ? [] + : T extends unknown[] + ? JsonifyList + : T extends readonly unknown[] + ? JsonifyList> + : T extends object + ? { + [K in keyof T]: [T[K]] extends [NotJsonable] | [never] + ? IsNotJsonableError + : Jsonlike; + } // JsonifyObject recursive call for its children + : IsNotJsonableError<'Passed value'>; + +export interface Invalid { + readonly __INVALID__: unique symbol; +} diff --git a/src/typed-fastify.ts b/src/typed-fastify.ts index 84e29ab..d7bdcca 100644 --- a/src/typed-fastify.ts +++ b/src/typed-fastify.ts @@ -1,5 +1,4 @@ import type * as F from 'fastify'; -import type { Jsonify } from 'type-fest'; import { RouteGenericInterface } from 'fastify/types/route'; import { RequestRouteOptions } from 'fastify/types/request'; @@ -10,6 +9,7 @@ import { ResolveFastifyRequestType, } from 'fastify/types/type-provider'; import { Operation, Schema } from './schema'; +import { Jsonlike, Id, Get2, Get, IsEqual, Invalid } from './type-utils'; const addSchema = < ServiceSchema extends Schema, @@ -225,7 +225,7 @@ interface Reply< ] : [Get2] extends [never] ? [] - : [Get2 | Jsonify>] + : [Jsonlike, true>] ): AsReply; readonly request: Request; @@ -283,10 +283,6 @@ type OpaqueReply< Opaque = Reply, > = Status extends unknown ? Opaque : Content extends unknown ? Opaque : Headers extends unknown ? Opaque : never; -interface Invalid { - readonly __INVALID__: unique symbol; -} - interface AsReply { readonly __REPLY_SYMBOL__: unique symbol; then(fulfilled: () => void, rejected: (err: Error) => void): void; @@ -296,8 +292,6 @@ export const asReply = (any: any) => { assertAsReply(any); return any; }; -type Get = P extends keyof T ? T[P] : never; -type Get2 = Get, P2>; interface Router { Querystring: Get; @@ -307,7 +301,6 @@ interface Router { // force reply to be never, as we expose it via custom reply interface Reply: never; } -type Id = T extends infer U ? { [K in keyof U]: U[K] } : never; interface Request< ServiceSchema extends Schema, @@ -330,9 +323,11 @@ interface Request< ROptions extends Omit, 'method' | 'url'> & { method: MP[0]; url: MP[1]; + body: Get; } = Omit, 'method' | 'url'> & { method: MP[0]; url: MP[1]; + body: Get; }, > extends Omit< F.FastifyRequest< @@ -350,15 +345,13 @@ interface Request< readonly operationPath: Path; readonly method: ROptions['method']; // A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request. - readonly body: ROptions['method'] extends 'GET' ? never : Jsonify>; + readonly body: ROptions['method'] extends 'GET' ? never : Jsonlike; readonly routeOptions: Id>; readonly routerMethod: ROptions['method']; readonly headers: Get; readonly routerPath: ROptions['url']; } -type IsEqual = (() => G extends T ? 1 : 2) extends () => G extends U ? 1 : 2 ? true : false; - type GetInvalidParamsValidation< Op extends Operation, Path extends keyof ServiceSchema['paths'], diff --git a/tap-snapshots/test/integration.test.ts.test.cjs b/tap-snapshots/test/integration.test.ts.test.cjs index 4cc6686..d00f562 100644 --- a/tap-snapshots/test/integration.test.ts.test.cjs +++ b/tap-snapshots/test/integration.test.ts.test.cjs @@ -1063,7 +1063,7 @@ exports[`test/integration.test.ts TAP it works with /jsonify > request path:POST Object { "Body": undefined, "Headers": Object { - "content-length": "35", + "content-length": "53", "content-type": "application/json", "host": "localhost:80", "user-agent": "lightMyRequest", @@ -1078,9 +1078,14 @@ Object { "format": "date-time", "type": "string", }, + "regexp": Object { + "format": "regex", + "type": "string", + }, }, "required": Array [ "date", + "regexp", ], "type": "object", }, @@ -1092,9 +1097,17 @@ Object { "format": "date-time", "type": "string", }, + "regexpType": Object { + "type": "string", + }, + "type": Object { + "type": "string", + }, }, "required": Array [ "date", + "regexpType", + "type", ], "type": "object", }, @@ -1108,13 +1121,15 @@ Object { "Headers": Array [ "HTTP/1.1 200 OK", "content-type: application/json; charset=utf-8", - "content-length: 35", + "content-length: 73", "Date: dateString", "Connection: keep-alive", ], "Payload": Array [ Object { - "date": "2023-10-28T13:31:57.949Z", + "date": "1970-01-01T00:00:00.000Z", + "regexpType": "string", + "type": "string", }, ], } @@ -1318,7 +1333,7 @@ Object { "Headers": Array [ "HTTP/1.1 200 OK", "content-type: application/json; charset=utf-8", - "content-length: 4316", + "content-length: 4445", "Date: dateString", "Connection: keep-alive", ], @@ -1592,9 +1607,14 @@ Object { "format": "date-time", "type": "string", }, + "regexp": Object { + "format": "regex", + "type": "string", + }, }, "required": Array [ "date", + "regexp", ], "type": "object", }, @@ -1610,9 +1630,17 @@ Object { "format": "date-time", "type": "string", }, + "regexpType": Object { + "type": "string", + }, + "type": Object { + "type": "string", + }, }, "required": Array [ "date", + "regexpType", + "type", ], "type": "object", }, @@ -2033,9 +2061,14 @@ Object { "format": "date-time", "type": "string", }, + "regexp": Object { + "format": "regex", + "type": "string", + }, }, "required": Array [ "date", + "regexp", ], "type": "object", }, @@ -2051,9 +2084,17 @@ Object { "format": "date-time", "type": "string", }, + "regexpType": Object { + "type": "string", + }, + "type": Object { + "type": "string", + }, }, "required": Array [ "date", + "regexpType", + "type", ], "type": "object", }, diff --git a/test/fixtures.ts b/test/fixtures.ts index 30c5245..350a5a5 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -14,7 +14,6 @@ class MyObjectId extends String implements ObjectId { return this.id; } } - export const defaultService: Service = { 'GET /': (req, reply) => { if (req.operationPath !== 'GET /') { @@ -29,9 +28,13 @@ export const defaultService: Service = { return reply.status(200).send([{ name: 'user1' }, { id: '1', type: 'TEST' }, { any: 'thing' }]); }, 'POST /jsonify': (req, reply) => { - const { date } = req.body; - date.charAt; // ok for string, not ok for Date - return reply.status(200).send({ date: new Date(date).toJSON() }); + const { date, regexp } = req.body; + + return reply.status(200).send({ + date: new Date(date), + type: typeof date, + regexpType: typeof regexp, + }); }, 'POST /': (req, reply) => { if (req.operationPath !== 'POST /') { diff --git a/test/integration.test.ts b/test/integration.test.ts index fefeb96..fda7298 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -6,7 +6,8 @@ import t from 'tap'; import addSchema from '../src'; import { defaultJsonSchema, defaultService } from './fixtures'; import fastifySwaggerUi from '@fastify/swagger-ui'; - +import formatsPlugin from 'ajv-formats'; +import keywordsPlugin from 'ajv-keywords'; type Test = (typeof tap)['Test']['prototype']; t.cleanSnapshot = (s) => { @@ -27,6 +28,9 @@ const buildApp = async ({ let stream = split(() => {}); const app = fastify({ + ajv: { + plugins: [formatsPlugin, [keywordsPlugin, ['typeof', 'instanceof']]], + }, logger: { stream, serializers: { @@ -419,3 +423,14 @@ t.test('it does not interfere with prefixed plugin', async (t) => { const res = await app.inject({ url: '/prefixed' }); t.same(res.body, 'true'); }); + +t.test('it works with /jsonify', async (t) => { + const app = await buildApp({ t }); + const res = await app.inject({ + url: '/jsonify', + method: 'POST', + payload: { date: new Date(0), regexp: /test/.toString() }, + }); + t.equal(res.statusCode, 200); + t.same(res.json(), { date: new Date(0).toJSON(), type: 'string', regexpType: 'string' }); +}); diff --git a/test/test_schema.gen.json b/test/test_schema.gen.json index ba4d29d..8ed1841 100644 --- a/test/test_schema.gen.json +++ b/test/test_schema.gen.json @@ -8,9 +8,11 @@ "required": [ "headers" ], + "additionalProperties": false, "properties": { "headers": { "type": "object", + "additionalProperties": false, "properties": { "authorization": { "type": "string" @@ -24,6 +26,7 @@ "required": [ "name" ], + "additionalProperties": false, "properties": { "name": { "type": "string" @@ -36,6 +39,7 @@ "id", "type" ], + "additionalProperties": false, "properties": { "type": { "type": "string" @@ -51,11 +55,16 @@ }, "TestObj": { "type": "object", - "$ref": "test_schema#/properties/Omit__Obj,\"type\"__", "required": [ + "id", "type" ], + "additionalProperties": false, "properties": { + "id": { + "format": "uuid", + "type": "string" + }, "type": { "type": "string", "const": "TEST" @@ -102,18 +111,6 @@ } } ] - }, - "Omit__Obj,\"type\"__": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "format": "uuid", - "type": "string" - } - } } }, "type": "object" @@ -121,15 +118,16 @@ "fastify": { "GET /": { "request": { - "$ref": "test_schema#/properties/SharedRequest", "type": "object", "required": [ "headers", "querystring" ], + "additionalProperties": false, "properties": { "querystring": { "type": "object", + "additionalProperties": false, "properties": { "getQueryParam": { "type": "boolean" @@ -142,6 +140,7 @@ "authorization", "getHeader" ], + "additionalProperties": false, "properties": { "authorization": { "type": "string" @@ -198,12 +197,14 @@ "required": [ "body" ], + "additionalProperties": false, "properties": { "body": { "type": "object", "required": [ "user" ], + "additionalProperties": false, "properties": { "user": { "$ref": "test_schema#/properties/User" @@ -219,6 +220,7 @@ "msg", "user" ], + "additionalProperties": false, "properties": { "user": { "$ref": "test_schema#/properties/User" @@ -236,16 +238,23 @@ "required": [ "body" ], + "additionalProperties": false, "properties": { "body": { "type": "object", "required": [ - "date" + "date", + "regexp" ], + "additionalProperties": false, "properties": { "date": { "type": "string", "format": "date-time" + }, + "regexp": { + "type": "string", + "format": "regex" } } } @@ -255,12 +264,21 @@ "200": { "type": "object", "required": [ - "date" + "date", + "regexpType", + "type" ], + "additionalProperties": false, "properties": { "date": { "type": "string", "format": "date-time" + }, + "type": { + "type": "string" + }, + "regexpType": { + "type": "string" } } } @@ -280,6 +298,7 @@ "required": [ "params" ], + "additionalProperties": false, "properties": { "params": { "type": "object", @@ -287,6 +306,7 @@ "id", "subid" ], + "additionalProperties": false, "properties": { "id": { "type": "number" @@ -308,6 +328,7 @@ "required": [ "frame" ], + "additionalProperties": false, "properties": { "frame": { "$ref": "test_schema#/properties/TestObj" @@ -322,12 +343,14 @@ "required": [ "params" ], + "additionalProperties": false, "properties": { "params": { "type": "object", "required": [ "castedToNumber" ], + "additionalProperties": true, "properties": { "castedToNumber": { "type": "number" @@ -348,12 +371,14 @@ "required": [ "querystring" ], + "additionalProperties": false, "properties": { "querystring": { "type": "object", "required": [ "match" ], + "additionalProperties": false, "properties": { "match": { "type": "string" @@ -368,6 +393,7 @@ "required": [ "value" ], + "additionalProperties": false, "properties": { "value": { "enum": [ @@ -386,12 +412,14 @@ "required": [ "querystring" ], + "additionalProperties": false, "properties": { "querystring": { "type": "object", "required": [ "reply" ], + "additionalProperties": false, "properties": { "reply": { "type": "string" @@ -406,6 +434,7 @@ "required": [ "value" ], + "additionalProperties": false, "properties": { "value": { "type": "string", @@ -423,6 +452,7 @@ "required": [ "id" ], + "additionalProperties": false, "properties": { "id": { "type": "string" @@ -432,5 +462,5 @@ } } }, - "$hash": "1252b062241bb72c9f02a27d8f3c6cee7f3bd8a1e7a8142a5a4b88038790d19f__v1.2.0" + "$hash": "38609094e259b0406fb6727e3c67af0ce5bad2bc2e945b8df8762d7f3bdf61c7__v1.2.0" } \ No newline at end of file diff --git a/test/test_schema.ts b/test/test_schema.ts index e2bb18c..07749ee 100644 --- a/test/test_schema.ts +++ b/test/test_schema.ts @@ -73,12 +73,21 @@ export interface TestSchema extends Schema { 'POST /jsonify': { request: { body: { + /** + * @type string + * @format date-time + */ date: Date; + /** + * @type string + * @format regex + */ + regexp: string; }; }; response: { 200: { - content: { date: Date }; + content: { date: Date; type: string; regexpType: string }; }; }; }; @@ -115,6 +124,7 @@ export interface TestSchema extends Schema { }; 'GET /inferredParams/:id/:castedToNumber': { request: { + /** @additionalProperties true */ params: { castedToNumber: number; }; diff --git a/test/typed-fastify.test-d.ts b/test/typed-fastify.test-d.ts index d88fa98..a9c8b6d 100644 --- a/test/typed-fastify.test-d.ts +++ b/test/typed-fastify.test-d.ts @@ -1,5 +1,6 @@ import { expectAssignable, expectType, expectNotType } from 'tsd'; import type { RequestHandler, Schema, Service } from '../src'; +import { Jsonlike, Invalid } from '../src/type-utils'; type User = { name: string; @@ -720,3 +721,52 @@ expectType>({ return reply.status(200).send(); }, }); + +function verifyJsonlike< + Input, + Expected extends Jsonlike, + DoNotCastToPrimitive extends boolean = false, +>() {} + +verifyJsonlike< + { + a: string; + b: { + toJSON(): number; + }; + c: { + toJSON(): { + toJSON(): string; + }; + }; + d: Date; + A: RegExp; + B: Function; + C: () => 1; + D: undefined; + }, + { + a: string; + b: number; + c: string; + d: string; + A: Invalid<'A is not Json-like'>; + B: Invalid<'B is not Json-like'>; + C: Invalid<'C is not Json-like'>; + D: Invalid<'D is not Json-like'>; + } +>(); + +verifyJsonlike< + { + a: Date; + b: Number; + A: RegExp; + }, + { + a: Date; + b: number; + A: Invalid<'A is not Json-like'>; + }, + true +>(); From 6bf1606e29b9c7c9248e7e0bdd419f44dd5ddb78 Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Sun, 29 Oct 2023 06:42:19 +0200 Subject: [PATCH 05/11] fix: fix type imports --- src/type-utils.ts | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/type-utils.ts b/src/type-utils.ts index a946a1f..0323d84 100644 --- a/src/type-utils.ts +++ b/src/type-utils.ts @@ -1,10 +1,14 @@ -import { PositiveInfinity, NegativeInfinity } from 'type-fest/source/numeric'; -import { JsonPrimitive, JsonValue } from 'type-fest/source/basic'; -import { EmptyObject } from 'type-fest/source/empty-object'; -import { TypedArray } from 'type-fest/source/typed-array'; -import { JsonifyList } from 'type-fest/source/jsonify'; -import { WritableDeep } from 'type-fest/source/writable-deep'; - +import type { + PositiveInfinity, + NegativeInfinity, + JsonPrimitive, + JsonValue, + EmptyObject, + TypedArray, + WritableDeep, + IsNever, + IsUnknown, +} from 'type-fest'; export type Id = T extends infer U ? { [K in keyof U]: U[K] } : never; export type Get = P extends keyof T ? T[P] : never; export type Get2 = Get, P2>; @@ -14,6 +18,17 @@ type IsNotJsonableError = Invalid<`${Extract} is not Json-like`> & type NotJsonable = ((...arguments_: any[]) => any) | undefined | symbol | RegExp | Function; +type NeverToNull = IsNever extends true ? null : T; + +// Handles tuples and arrays +type JsonlikeList = T extends [] + ? [] + : T extends [infer F, ...infer R] + ? [NeverToNull>, ...JsonlikeList] + : IsUnknown extends true + ? [] + : Array>; + // tweaked version of Jsonify from type-fest export type Jsonlike = T extends PositiveInfinity | NegativeInfinity ? null @@ -44,9 +59,9 @@ export type Jsonlike = T extend : T extends [] ? [] : T extends unknown[] - ? JsonifyList + ? JsonlikeList : T extends readonly unknown[] - ? JsonifyList> + ? JsonlikeList, DoNotCastToPrimitive> : T extends object ? { [K in keyof T]: [T[K]] extends [NotJsonable] | [never] From e88fa4e4ed1ab86a2a61081fc851b22a2a3407c0 Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Sun, 29 Oct 2023 06:56:30 +0200 Subject: [PATCH 06/11] refactor: Updated type parameter name for consistency The commit refactors the code by updating the type parameter name from "DoNotCastToPrimitive" to "CastBehavior" in order to improve code clarity and consistency. --- src/type-utils.ts | 18 +++++++++--------- src/typed-fastify.ts | 4 ++-- test/typed-fastify.test-d.ts | 7 ++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/type-utils.ts b/src/type-utils.ts index 0323d84..67d5d46 100644 --- a/src/type-utils.ts +++ b/src/type-utils.ts @@ -20,8 +20,10 @@ type NotJsonable = ((...arguments_: any[]) => any) | undefined | symbol | RegExp type NeverToNull = IsNever extends true ? null : T; +type JsonCastBehavior = 'cast' | 'no-cast'; + // Handles tuples and arrays -type JsonlikeList = T extends [] +type JsonlikeList = T extends [] ? [] : T extends [infer F, ...infer R] ? [NeverToNull>, ...JsonlikeList] @@ -30,7 +32,7 @@ type JsonlikeList = T : Array>; // tweaked version of Jsonify from type-fest -export type Jsonlike = T extends PositiveInfinity | NegativeInfinity +export type Jsonlike = T extends PositiveInfinity | NegativeInfinity ? null : T extends NotJsonable ? IsNotJsonableError<'Passed value'> @@ -41,10 +43,10 @@ export type Jsonlike = T extend toJSON(): infer J; } ? (() => J) extends () => JsonValue // Is J assignable to JsonValue? - ? DoNotCastToPrimitive extends true + ? CastBehavior extends 'no-cast' ? T : J // Then T is Jsonable and its Jsonable value is J - : Jsonlike // Maybe if we look a level deeper we'll find a JsonValue + : Jsonlike // Maybe if we look a level deeper we'll find a JsonValue : // Instanced primitives are objects T extends Number ? number @@ -59,14 +61,12 @@ export type Jsonlike = T extend : T extends [] ? [] : T extends unknown[] - ? JsonlikeList + ? JsonlikeList : T extends readonly unknown[] - ? JsonlikeList, DoNotCastToPrimitive> + ? JsonlikeList, CastBehavior> : T extends object ? { - [K in keyof T]: [T[K]] extends [NotJsonable] | [never] - ? IsNotJsonableError - : Jsonlike; + [K in keyof T]: [T[K]] extends [NotJsonable] | [never] ? IsNotJsonableError : Jsonlike; } // JsonifyObject recursive call for its children : IsNotJsonableError<'Passed value'>; diff --git a/src/typed-fastify.ts b/src/typed-fastify.ts index d7bdcca..eefaf54 100644 --- a/src/typed-fastify.ts +++ b/src/typed-fastify.ts @@ -225,7 +225,7 @@ interface Reply< ] : [Get2] extends [never] ? [] - : [Jsonlike, true>] + : [Jsonlike, 'no-cast'>] ): AsReply; readonly request: Request; @@ -345,7 +345,7 @@ interface Request< readonly operationPath: Path; readonly method: ROptions['method']; // A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request. - readonly body: ROptions['method'] extends 'GET' ? never : Jsonlike; + readonly body: ROptions['method'] extends 'GET' ? never : Jsonlike; readonly routeOptions: Id>; readonly routerMethod: ROptions['method']; readonly headers: Get; diff --git a/test/typed-fastify.test-d.ts b/test/typed-fastify.test-d.ts index a9c8b6d..bfdf2e2 100644 --- a/test/typed-fastify.test-d.ts +++ b/test/typed-fastify.test-d.ts @@ -725,7 +725,7 @@ expectType>({ function verifyJsonlike< Input, Expected extends Jsonlike, - DoNotCastToPrimitive extends boolean = false, + DoNotCastToPrimitive extends 'cast' | 'no-cast', >() {} verifyJsonlike< @@ -754,7 +754,8 @@ verifyJsonlike< B: Invalid<'B is not Json-like'>; C: Invalid<'C is not Json-like'>; D: Invalid<'D is not Json-like'>; - } + }, + 'cast' >(); verifyJsonlike< @@ -768,5 +769,5 @@ verifyJsonlike< b: number; A: Invalid<'A is not Json-like'>; }, - true + 'no-cast' >(); From 4f32cd4da418c97833c3ae431bc6d4e6aa7e53e7 Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Mon, 30 Oct 2023 05:01:00 +0200 Subject: [PATCH 07/11] test: check for stringified payload in jsonify --- .../test/integration.test.ts.test.cjs | 76 +++++++++++++++++++ test/integration.test.ts | 15 +++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/tap-snapshots/test/integration.test.ts.test.cjs b/tap-snapshots/test/integration.test.ts.test.cjs index d00f562..7753016 100644 --- a/tap-snapshots/test/integration.test.ts.test.cjs +++ b/tap-snapshots/test/integration.test.ts.test.cjs @@ -1059,6 +1059,82 @@ Object { } ` +exports[`test/integration.test.ts TAP it works with /jsonify 2 > request path:POST /jsonify id:req-1 1`] = ` +Object { + "Body": undefined, + "Headers": Object { + "content-length": "47", + "content-type": "application/json", + "host": "localhost:80", + "user-agent": "lightMyRequest", + }, + "Params": Object {}, + "Query": Object {}, + "schema": Object { + "body": Object { + "additionalProperties": false, + "properties": Object { + "date": Object { + "format": "date-time", + "type": "string", + }, + "regexp": Object { + "format": "regex", + "type": "string", + }, + }, + "required": Array [ + "date", + "regexp", + ], + "type": "object", + }, + "response": Object { + "200": Object { + "additionalProperties": false, + "properties": Object { + "date": Object { + "format": "date-time", + "type": "string", + }, + "regexpType": Object { + "type": "string", + }, + "type": Object { + "type": "string", + }, + }, + "required": Array [ + "date", + "regexpType", + "type", + ], + "type": "object", + }, + }, + }, +} +` + +exports[`test/integration.test.ts TAP it works with /jsonify 2 > response path:POST /jsonify id:req-1 1`] = ` +Object { + "Headers": Array [ + "HTTP/1.1 200 OK", + "content-type: application/json; charset=utf-8", + "content-length: 73", + "Date: dateString", + "Connection: keep-alive", + ], + "Payload": Array [ + Object { + "date": "1970-01-01T00:00:00.000Z", + "regexpType": "string", + "type": "string", + }, + ], +} +` + exports[`test/integration.test.ts TAP it works with /jsonify > request path:POST /jsonify id:req-1 1`] = ` Object { "Body": undefined, diff --git a/test/integration.test.ts b/test/integration.test.ts index fda7298..55eafdf 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -410,7 +410,7 @@ t.test('it does not interfere with prefixed plugin', async (t) => { }, }, }; - app.get('/', { schema }, (req, reply) => { + app.get('/', { schema }, (req) => { return String(req.routeOptions.schema === schema); }); done(); @@ -431,6 +431,19 @@ t.test('it works with /jsonify', async (t) => { method: 'POST', payload: { date: new Date(0), regexp: /test/.toString() }, }); + t.equal(res.statusCode, 200); t.same(res.json(), { date: new Date(0).toJSON(), type: 'string', regexpType: 'string' }); }); +t.test('it works with /jsonify 2', async (t) => { + const app = await buildApp({ t }); + const date = new Date(0).toJSON(); + const res2 = await app.inject({ + url: '/jsonify', + method: 'POST', + payload: { date, regexp: '' }, + }); + t.equal(res2.statusCode, 200); + + t.same(res2.json(), { date, type: 'string', regexpType: 'string' }); +}); From b8716f670ceb66b623b7d740697c3991493eef9c Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Mon, 30 Oct 2023 05:40:38 +0200 Subject: [PATCH 08/11] fix: change Jsonlike behavior for response body to allow both JsonValue and original type --- src/type-utils.ts | 6 ++-- src/typed-fastify.ts | 2 +- .../test/integration.test.ts.test.cjs | 32 +++++++++++++++++-- test/fixtures.ts | 1 + test/integration.test.ts | 9 ++++-- test/test_schema.gen.json | 7 +++- test/test_schema.ts | 7 +++- 7 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/type-utils.ts b/src/type-utils.ts index 67d5d46..4edf993 100644 --- a/src/type-utils.ts +++ b/src/type-utils.ts @@ -20,7 +20,7 @@ type NotJsonable = ((...arguments_: any[]) => any) | undefined | symbol | RegExp type NeverToNull = IsNever extends true ? null : T; -type JsonCastBehavior = 'cast' | 'no-cast'; +type JsonCastBehavior = 'cast' | 'combine'; // Handles tuples and arrays type JsonlikeList = T extends [] @@ -43,8 +43,8 @@ export type Jsonlike = T extends Posit toJSON(): infer J; } ? (() => J) extends () => JsonValue // Is J assignable to JsonValue? - ? CastBehavior extends 'no-cast' - ? T + ? CastBehavior extends 'combine' + ? T | J : J // Then T is Jsonable and its Jsonable value is J : Jsonlike // Maybe if we look a level deeper we'll find a JsonValue : // Instanced primitives are objects diff --git a/src/typed-fastify.ts b/src/typed-fastify.ts index eefaf54..119f9cb 100644 --- a/src/typed-fastify.ts +++ b/src/typed-fastify.ts @@ -225,7 +225,7 @@ interface Reply< ] : [Get2] extends [never] ? [] - : [Jsonlike, 'no-cast'>] + : [Jsonlike, 'combine'>] ): AsReply; readonly request: Request; diff --git a/tap-snapshots/test/integration.test.ts.test.cjs b/tap-snapshots/test/integration.test.ts.test.cjs index 7753016..05d99b5 100644 --- a/tap-snapshots/test/integration.test.ts.test.cjs +++ b/tap-snapshots/test/integration.test.ts.test.cjs @@ -1097,6 +1097,11 @@ Object { "format": "date-time", "type": "string", }, + "dateString": Object { + "format": "date-time", + "maxLength": 5, + "type": "string", + }, "regexpType": Object { "type": "string", }, @@ -1106,6 +1111,7 @@ Object { }, "required": Array [ "date", + "dateString", "regexpType", "type", ], @@ -1121,13 +1127,14 @@ Object { "Headers": Array [ "HTTP/1.1 200 OK", "content-type: application/json; charset=utf-8", - "content-length: 73", + "content-length: 104", "Date: dateString", "Connection: keep-alive", ], "Payload": Array [ Object { "date": "1970-01-01T00:00:00.000Z", + "dateString": "Thu Jan 01 1970", "regexpType": "string", "type": "string", }, @@ -1173,6 +1180,11 @@ Object { "format": "date-time", "type": "string", }, + "dateString": Object { + "format": "date-time", + "maxLength": 5, + "type": "string", + }, "regexpType": Object { "type": "string", }, @@ -1182,6 +1194,7 @@ Object { }, "required": Array [ "date", + "dateString", "regexpType", "type", ], @@ -1197,13 +1210,14 @@ Object { "Headers": Array [ "HTTP/1.1 200 OK", "content-type: application/json; charset=utf-8", - "content-length: 73", + "content-length: 104", "Date: dateString", "Connection: keep-alive", ], "Payload": Array [ Object { "date": "1970-01-01T00:00:00.000Z", + "dateString": "Thu Jan 01 1970", "regexpType": "string", "type": "string", }, @@ -1409,7 +1423,7 @@ Object { "Headers": Array [ "HTTP/1.1 200 OK", "content-type: application/json; charset=utf-8", - "content-length: 4445", + "content-length: 4524", "Date: dateString", "Connection: keep-alive", ], @@ -1706,6 +1720,11 @@ Object { "format": "date-time", "type": "string", }, + "dateString": Object { + "format": "date-time", + "maxLength": 5, + "type": "string", + }, "regexpType": Object { "type": "string", }, @@ -1715,6 +1734,7 @@ Object { }, "required": Array [ "date", + "dateString", "regexpType", "type", ], @@ -2160,6 +2180,11 @@ Object { "format": "date-time", "type": "string", }, + "dateString": Object { + "format": "date-time", + "maxLength": 5, + "type": "string", + }, "regexpType": Object { "type": "string", }, @@ -2169,6 +2194,7 @@ Object { }, "required": Array [ "date", + "dateString", "regexpType", "type", ], diff --git a/test/fixtures.ts b/test/fixtures.ts index 350a5a5..e83815f 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -32,6 +32,7 @@ export const defaultService: Service = { return reply.status(200).send({ date: new Date(date), + dateString: new Date(date).toDateString(), type: typeof date, regexpType: typeof regexp, }); diff --git a/test/integration.test.ts b/test/integration.test.ts index 55eafdf..e13c900 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -433,7 +433,12 @@ t.test('it works with /jsonify', async (t) => { }); t.equal(res.statusCode, 200); - t.same(res.json(), { date: new Date(0).toJSON(), type: 'string', regexpType: 'string' }); + t.same(res.json(), { + date: new Date(0).toJSON(), + dateString: 'Thu Jan 01 1970', + type: 'string', + regexpType: 'string', + }); }); t.test('it works with /jsonify 2', async (t) => { const app = await buildApp({ t }); @@ -445,5 +450,5 @@ t.test('it works with /jsonify 2', async (t) => { }); t.equal(res2.statusCode, 200); - t.same(res2.json(), { date, type: 'string', regexpType: 'string' }); + t.same(res2.json(), { date, dateString: 'Thu Jan 01 1970', type: 'string', regexpType: 'string' }); }); diff --git a/test/test_schema.gen.json b/test/test_schema.gen.json index 8ed1841..3cbb412 100644 --- a/test/test_schema.gen.json +++ b/test/test_schema.gen.json @@ -265,6 +265,7 @@ "type": "object", "required": [ "date", + "dateString", "regexpType", "type" ], @@ -274,6 +275,10 @@ "type": "string", "format": "date-time" }, + "dateString": { + "type": "string", + "format": "date-time" + }, "type": { "type": "string" }, @@ -462,5 +467,5 @@ } } }, - "$hash": "38609094e259b0406fb6727e3c67af0ce5bad2bc2e945b8df8762d7f3bdf61c7__v1.2.0" + "$hash": "09d762cb9c53f4338c7374249078c983ab9ddf1a0814f98d7854a4e15f301e7b__v1.2.0" } \ No newline at end of file diff --git a/test/test_schema.ts b/test/test_schema.ts index 07749ee..ac7287b 100644 --- a/test/test_schema.ts +++ b/test/test_schema.ts @@ -87,7 +87,12 @@ export interface TestSchema extends Schema { }; response: { 200: { - content: { date: Date; type: string; regexpType: string }; + content: { + date: Date; + dateString: Date; + type: string; + regexpType: string; + }; }; }; }; From 69581c431d89b60b011acf319eabe055c460d629 Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Mon, 30 Oct 2023 05:45:26 +0200 Subject: [PATCH 09/11] test: fix d.ts tests --- test/typed-fastify.test-d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/typed-fastify.test-d.ts b/test/typed-fastify.test-d.ts index bfdf2e2..1b745a7 100644 --- a/test/typed-fastify.test-d.ts +++ b/test/typed-fastify.test-d.ts @@ -725,7 +725,7 @@ expectType>({ function verifyJsonlike< Input, Expected extends Jsonlike, - DoNotCastToPrimitive extends 'cast' | 'no-cast', + DoNotCastToPrimitive extends 'cast' | 'combine', >() {} verifyJsonlike< @@ -769,5 +769,5 @@ verifyJsonlike< b: number; A: Invalid<'A is not Json-like'>; }, - 'no-cast' + 'combine' >(); From 3cd6b611c0360ef3425977c3d8135c7a54f24963 Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Mon, 30 Oct 2023 05:52:14 +0200 Subject: [PATCH 10/11] test: fix snaps --- tap-snapshots/test/integration.test.ts.test.cjs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tap-snapshots/test/integration.test.ts.test.cjs b/tap-snapshots/test/integration.test.ts.test.cjs index 05d99b5..80c7cd8 100644 --- a/tap-snapshots/test/integration.test.ts.test.cjs +++ b/tap-snapshots/test/integration.test.ts.test.cjs @@ -1099,7 +1099,6 @@ Object { }, "dateString": Object { "format": "date-time", - "maxLength": 5, "type": "string", }, "regexpType": Object { @@ -1182,7 +1181,6 @@ Object { }, "dateString": Object { "format": "date-time", - "maxLength": 5, "type": "string", }, "regexpType": Object { @@ -1423,7 +1421,7 @@ Object { "Headers": Array [ "HTTP/1.1 200 OK", "content-type: application/json; charset=utf-8", - "content-length: 4524", + "content-length: 4510", "Date: dateString", "Connection: keep-alive", ], @@ -1722,7 +1720,6 @@ Object { }, "dateString": Object { "format": "date-time", - "maxLength": 5, "type": "string", }, "regexpType": Object { @@ -2182,7 +2179,6 @@ Object { }, "dateString": Object { "format": "date-time", - "maxLength": 5, "type": "string", }, "regexpType": Object { From df4c9f6d19dcc5e0f40dc4c2c6ab60b09cc512b8 Mon Sep 17 00:00:00 2001 From: Alexander Ryzhikov Date: Mon, 30 Oct 2023 06:28:05 +0200 Subject: [PATCH 11/11] test: use dot reporter by default --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc563d0..0e2dc6a 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "pretest": "tsnd generator/gen.bin.ts gen 'test/test_schema.ts'", "server": "pnpm preserver && tsnd test/server.ts", "test": "pnpm pretest && pnpm test:types && pnpm test:integration", - "test:integration": "TAP_TS=1 tap test/*.test.ts -R terse", + "test:integration": "TAP_TS=1 tap test/*.test.ts -R dot", "test:types": "tsc -p test/tsconfig.test.json" } }