snap-on-openapi 1.0.1 → 1.0.3

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.
Files changed (91) hide show
  1. package/README.md +15 -15
  2. package/dist/OpenApi.d.ts +23 -23
  3. package/dist/OpenApi.js +17 -17
  4. package/dist/OpenApi.test.d.ts +1 -0
  5. package/dist/OpenApi.test.js +418 -0
  6. package/dist/README.md +15 -15
  7. package/dist/index.d.ts +38 -38
  8. package/dist/index.js +22 -22
  9. package/dist/services/ClientGenerator/ClientGenerator.d.ts +2 -2
  10. package/dist/services/ClientGenerator/ClientGenerator.test.d.ts +1 -0
  11. package/dist/services/ClientGenerator/ClientGenerator.test.js +37 -0
  12. package/dist/services/ConfigBuilder/ConfigBuilder.d.ts +9 -9
  13. package/dist/services/ConfigBuilder/ConfigBuilder.js +1 -1
  14. package/dist/services/ConfigBuilder/ConfigBuilder.test.d.ts +1 -0
  15. package/dist/services/ConfigBuilder/ConfigBuilder.test.js +207 -0
  16. package/dist/services/ConfigBuilder/types/DefaultConfig.d.ts +9 -9
  17. package/dist/services/ConfigBuilder/types/DefaultConfig.js +6 -6
  18. package/dist/services/ConfigBuilder/types/DefaultConfig.test.d.ts +1 -0
  19. package/dist/services/ConfigBuilder/types/DefaultConfig.test.js +82 -0
  20. package/dist/services/ConfigBuilder/types/DefaultErrorMap.d.ts +7 -7
  21. package/dist/services/ConfigBuilder/types/DefaultErrorMap.js +4 -4
  22. package/dist/services/ConfigBuilder/types/DefaultRouteContextMap.d.ts +3 -3
  23. package/dist/services/ConfigBuilder/types/DefaultRouteMap.d.ts +5 -5
  24. package/dist/services/ConfigBuilder/types/DefaultRouteMap.js +2 -2
  25. package/dist/services/ConfigBuilder/types/DefaultRouteParamsMap.d.ts +2 -2
  26. package/dist/services/ConfigBuilder/types/OpenApiConstructor.d.ts +1 -1
  27. package/dist/services/DescriptionChecker/DescriptionChecker.d.ts +2 -2
  28. package/dist/services/DescriptionChecker/DescriptionChecker.js +1 -1
  29. package/dist/services/DescriptionChecker/DescriptionChecker.test.d.ts +1 -0
  30. package/dist/services/DescriptionChecker/DescriptionChecker.test.js +120 -0
  31. package/dist/services/DevelopmentUtils/DevelopmentUtils.test.d.ts +1 -0
  32. package/dist/services/DevelopmentUtils/DevelopmentUtils.test.js +10 -0
  33. package/dist/services/ExpressWrapper/ExpressWrapper.d.ts +5 -5
  34. package/dist/services/ExpressWrapper/ExpressWrapper.js +1 -1
  35. package/dist/services/ExpressWrapper/ExpressWrapper.test.d.ts +1 -0
  36. package/dist/services/ExpressWrapper/ExpressWrapper.test.js +136 -0
  37. package/dist/services/ExpressWrapper/types/ExpressApp.d.ts +1 -1
  38. package/dist/services/ExpressWrapper/types/ExpressHandler.d.ts +2 -2
  39. package/dist/services/Logger/Logger.d.ts +2 -1
  40. package/dist/services/Logger/Logger.js +4 -1
  41. package/dist/services/Logger/Logger.test.d.ts +1 -0
  42. package/dist/services/Logger/Logger.test.js +113 -0
  43. package/dist/services/RoutingFactory/RoutingFactory.d.ts +4 -4
  44. package/dist/services/SchemaGenerator/SchemaGenerator.d.ts +6 -6
  45. package/dist/services/SchemaGenerator/SchemaGenerator.js +3 -4
  46. package/dist/services/SchemaGenerator/SchemaGenerator.test.d.ts +1 -0
  47. package/dist/services/SchemaGenerator/SchemaGenerator.test.js +142 -0
  48. package/dist/services/TanstackStartWrapper/TanstackStartWrapper.d.ts +4 -4
  49. package/dist/services/TanstackStartWrapper/TanstackStartWrapper.js +1 -1
  50. package/dist/services/TanstackStartWrapper/TanstackStartWrapper.test.d.ts +1 -0
  51. package/dist/services/TanstackStartWrapper/TanstackStartWrapper.test.js +44 -0
  52. package/dist/services/TestUtils/TestUtils.d.ts +5 -5
  53. package/dist/services/TestUtils/TestUtils.js +3 -3
  54. package/dist/services/TestUtils/TestUtils.test.d.ts +1 -0
  55. package/dist/services/TestUtils/TestUtils.test.js +20 -0
  56. package/dist/services/ValidationUtils/ValidationUtils.js +3 -3
  57. package/dist/services/ValidationUtils/ValidationUtils.test.d.ts +1 -0
  58. package/dist/services/ValidationUtils/ValidationUtils.test.js +165 -0
  59. package/dist/services/ValidationUtils/transformers/stringBooleanTransformer.test.d.ts +1 -0
  60. package/dist/services/ValidationUtils/transformers/stringBooleanTransformer.test.js +19 -0
  61. package/dist/services/ValidationUtils/transformers/stringDateTransformer.test.d.ts +1 -0
  62. package/dist/services/ValidationUtils/transformers/stringDateTransformer.test.js +13 -0
  63. package/dist/services/ValidationUtils/transformers/stringNumberTransfromer.test.d.ts +1 -0
  64. package/dist/services/ValidationUtils/transformers/stringNumberTransfromer.test.js +47 -0
  65. package/dist/types/AnyRoute.d.ts +1 -1
  66. package/dist/types/InitialBuilder.d.ts +8 -8
  67. package/dist/types/Route.d.ts +2 -2
  68. package/dist/types/RouteMap.d.ts +2 -2
  69. package/dist/types/Wrappers.d.ts +3 -3
  70. package/dist/types/config/AnyConfig.d.ts +3 -3
  71. package/dist/types/config/AnyRouteConfigMap.d.ts +1 -1
  72. package/dist/types/config/Config.d.ts +10 -8
  73. package/dist/types/config/ContextParams.d.ts +2 -2
  74. package/dist/types/config/ErrorConfigMap.d.ts +1 -1
  75. package/dist/types/config/ErrorResponse.d.ts +1 -1
  76. package/dist/types/config/Info.d.ts +1 -1
  77. package/dist/types/config/RouteConfig.d.ts +1 -1
  78. package/dist/types/config/RouteConfigMap.d.ts +3 -3
  79. package/dist/types/config/RouteContextMap.d.ts +2 -2
  80. package/dist/types/config/Server.d.ts +1 -1
  81. package/dist/types/errors/BuiltInError.d.ts +2 -2
  82. package/dist/types/errors/BuiltInError.js +1 -1
  83. package/dist/types/errors/ValidationError.d.ts +2 -2
  84. package/dist/types/errors/ValidationError.js +2 -2
  85. package/dist/types/errors/responses/NotFoundErrorResponse.d.ts +1 -1
  86. package/dist/types/errors/responses/NotFoundErrorResponse.js +1 -1
  87. package/dist/types/errors/responses/UnknownErrorResponse.d.ts +1 -1
  88. package/dist/types/errors/responses/UnknownErrorResponse.js +1 -1
  89. package/dist/types/errors/responses/ValidationErrorResponse.d.ts +2 -2
  90. package/dist/types/errors/responses/ValidationErrorResponse.js +3 -3
  91. package/package.json +1 -1
@@ -0,0 +1,113 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'vitest';
2
+ import { Logger } from './Logger';
3
+ import { LogLevel } from './types/LogLevel';
4
+ describe('Logger', () => {
5
+ const consoleLogBackup = console.log;
6
+ const consoleDirBackup = console.dir;
7
+ let messages = [];
8
+ const fakeLog = (msg) => {
9
+ messages.push(msg);
10
+ consoleLogBackup(msg);
11
+ };
12
+ const fakeDir = (msg) => {
13
+ messages.push(msg);
14
+ consoleDirBackup(msg);
15
+ };
16
+ beforeEach(() => {
17
+ console.log = fakeLog;
18
+ console.dir = fakeDir;
19
+ messages = [];
20
+ Logger.showTime = true;
21
+ Logger.setLogLevel(LogLevel.all);
22
+ });
23
+ afterEach(() => {
24
+ console.log = consoleLogBackup;
25
+ console.dir = consoleDirBackup;
26
+ });
27
+ test('Logs output to console', async () => {
28
+ const logger = new Logger('invoker');
29
+ const now = new Date();
30
+ logger.info('Hello there');
31
+ const nowStr = now.toISOString();
32
+ expect(messages[messages.length - 1]).toContain('Hello there');
33
+ expect(messages[messages.length - 1]).toContain(nowStr.substring(0, nowStr.length - 4));
34
+ expect(messages[messages.length - 1]).toContain('[invoker]');
35
+ expect(messages[messages.length - 1]).toContain('[info]');
36
+ });
37
+ test('Time can be omitted', async () => {
38
+ const logger = new Logger('invoker');
39
+ const now = new Date();
40
+ logger.info('Hello there');
41
+ const nowStr = now.toISOString();
42
+ expect(messages[messages.length - 1]).toContain(nowStr.substring(0, nowStr.length - 4));
43
+ messages = [];
44
+ Logger.showTime = false;
45
+ logger.info('Hello there');
46
+ expect(messages[messages.length - 1]).not.toContain(nowStr.substring(0, nowStr.length - 4));
47
+ });
48
+ test('Outputs data to console', async () => {
49
+ const logger = new Logger('invoker');
50
+ logger.info('Hello there', { myNameIs: 'Alex' });
51
+ consoleLogBackup(messages);
52
+ expect(JSON.stringify(messages[messages.length - 1])).toContain('myNameIs');
53
+ expect(JSON.stringify(messages[messages.length - 1])).toContain('Alex');
54
+ expect(messages[messages.length - 2]).toContain('Hello there');
55
+ });
56
+ test('Outputs error messages', async () => {
57
+ const logger = new Logger('invoker2');
58
+ logger.error('Hello myerror', new Error('My error message'));
59
+ expect(messages[messages.length - 1]?.toString()).toContain('My error message');
60
+ expect(messages[messages.length - 2]).toContain('Hello myerror');
61
+ expect(messages[messages.length - 2]).toContain('[error]');
62
+ expect(messages[messages.length - 2]).toContain('[invoker2]');
63
+ });
64
+ test('Outputs message from error if no message given', async () => {
65
+ const logger = new Logger('invoker2');
66
+ logger.error(null, new Error('My error message'));
67
+ expect(messages[messages.length - 1]?.toString()).toContain('My error message');
68
+ expect(messages[messages.length - 2]).toContain('My error message');
69
+ expect(messages[messages.length - 2]).toContain('[error]');
70
+ expect(messages[messages.length - 2]).toContain('[invoker2]');
71
+ });
72
+ test('Silent on info and debug if error log level is set', async () => {
73
+ const logger = new Logger('invoker2');
74
+ logger.info('My info message');
75
+ expect(messages[messages.length - 1]).toContain('My info message');
76
+ messages = [];
77
+ Logger.setLogLevel(LogLevel.error);
78
+ logger.info('My info message');
79
+ expect(messages[messages.length - 1]).toBe(undefined);
80
+ logger.info('My info message');
81
+ expect(messages[messages.length - 1]).toBe(undefined);
82
+ logger.error('My error message', new Error('asdas'));
83
+ expect(messages[messages.length - 2]).toContain('My error message');
84
+ });
85
+ test('Silent on debug if info log level is set', async () => {
86
+ const logger = new Logger('invoker2');
87
+ logger.debug('My debug message');
88
+ expect(messages[messages.length - 1]).toContain('My debug message');
89
+ messages = [];
90
+ Logger.setLogLevel(LogLevel.info);
91
+ logger.debug('My debug message');
92
+ expect(messages[messages.length - 1]).toBe(undefined);
93
+ logger.info('My info message');
94
+ expect(messages[messages.length - 1]).toContain('My info message');
95
+ logger.error('My error message', new Error('asdas'));
96
+ expect(messages[messages.length - 2]).toContain('My error message');
97
+ });
98
+ test('Returns correct invoker when extended', async () => {
99
+ const logger = new Logger('NewInvoker', 'OriginalInvoker');
100
+ expect(logger.getInvoker()).toBe('OriginalInvoker:NewInvoker');
101
+ });
102
+ test('Can Handle circular objects', async () => {
103
+ const logger = new Logger('invoker');
104
+ const obj = { myNameIs: 'Alex', x: [1, 2, 3] };
105
+ obj.obj = obj;
106
+ obj.arr = [obj, obj];
107
+ logger.info('Hello there', obj);
108
+ consoleLogBackup(messages);
109
+ expect(JSON.stringify(messages[messages.length - 1])).toContain('myNameIs');
110
+ expect(JSON.stringify(messages[messages.length - 1])).toContain('circular->obj');
111
+ expect(messages[messages.length - 2]).toContain('Hello there');
112
+ });
113
+ });
@@ -1,8 +1,8 @@
1
1
  import { ZodFirstPartySchemaTypes, ZodObject, ZodRawShape } from 'zod';
2
- import { Method } from '../../enums/Methods.js';
3
- import { Route } from '../../types/Route.js';
4
- import { AnyConfig } from '../../types/config/AnyConfig.js';
5
- import { RouteExtraProps } from '../../types/config/RouteExtraProps.js';
2
+ import { Method } from '../../enums/Methods';
3
+ import { Route } from '../../types/Route';
4
+ import { AnyConfig } from '../../types/config/AnyConfig';
5
+ import { RouteExtraProps } from '../../types/config/RouteExtraProps';
6
6
  export declare class RoutingFactory<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
7
7
  protected map: TConfig;
8
8
  constructor(map: TConfig);
@@ -1,17 +1,17 @@
1
1
  import 'zod-openapi/extend';
2
2
  import { createDocument, ZodOpenApiOperationObject } from 'zod-openapi';
3
- import { AnyRoute } from '../../types/AnyRoute.js';
4
- import { AnyConfig } from '../../types/config/AnyConfig.js';
5
- import { Logger } from '../Logger/Logger.js';
6
- import { Server } from '../../types/config/Server.js';
7
- import { Info } from '../../types/config/Info.js';
3
+ import { AnyRoute } from '../../types/AnyRoute';
4
+ import { AnyConfig } from '../../types/config/AnyConfig';
5
+ import { Logger } from '../Logger/Logger';
6
+ import { Server } from '../../types/config/Server';
7
+ import { Info } from '../../types/config/Info';
8
8
  export declare class SchemaGenerator<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
9
9
  protected logger: Logger;
10
10
  protected info: Info;
11
11
  protected routes: AnyRoute<TRouteTypes>[];
12
12
  protected servers: Server[];
13
13
  protected routeSpec: TConfig;
14
- constructor(invoker: string, info: Info, spec: TConfig, routes: AnyRoute<TRouteTypes>[], servers: Server[]);
14
+ constructor(logger: Logger, info: Info, spec: TConfig, routes: AnyRoute<TRouteTypes>[], servers: Server[]);
15
15
  getYaml(): string;
16
16
  saveYaml(path: string): void;
17
17
  protected createDocument(): ReturnType<typeof createDocument>;
@@ -3,16 +3,15 @@ import { writeFileSync } from 'fs';
3
3
  import { stringify } from 'yaml';
4
4
  import { createDocument } from 'zod-openapi';
5
5
  import { z } from 'zod';
6
- import { Method } from '../../enums/Methods.js';
7
- import { Logger } from '../Logger/Logger.js';
6
+ import { Method } from '../../enums/Methods';
8
7
  export class SchemaGenerator {
9
8
  logger;
10
9
  info;
11
10
  routes;
12
11
  servers;
13
12
  routeSpec;
14
- constructor(invoker, info, spec, routes, servers) {
15
- this.logger = new Logger(SchemaGenerator.name, invoker);
13
+ constructor(logger, info, spec, routes, servers) {
14
+ this.logger = logger;
16
15
  this.routes = routes;
17
16
  this.routeSpec = spec;
18
17
  this.servers = servers;
@@ -0,0 +1,142 @@
1
+ import { beforeEach, describe, expect, test } from 'vitest';
2
+ import { TestUtils } from '../TestUtils/TestUtils';
3
+ import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs';
4
+ import z from 'zod';
5
+ import { Method } from '../../enums/Methods';
6
+ import { SampleRouteType } from '../../enums/SampleRouteType';
7
+ import { OpenApi } from '../../OpenApi';
8
+ import { ErrorCode } from '../../enums/ErrorCode';
9
+ import { notFoundErrorResponseValidator } from '../../types/errors/responses/NotFoundErrorResponse';
10
+ import { validationErrorResponseValidator } from '../../types/errors/responses/ValidationErrorResponse';
11
+ import { unknownErrorResponseValidator } from '../../types/errors/responses/UnknownErrorResponse';
12
+ describe('SchemaGenerator', () => {
13
+ beforeEach(async () => {
14
+ if (!existsSync('temp')) {
15
+ mkdirSync('temp');
16
+ }
17
+ if (existsSync('temp/schema.yml')) {
18
+ rmSync('temp/schema.yml');
19
+ }
20
+ });
21
+ test('Can create error unions on same status', async () => {
22
+ const api = OpenApi.builder.customizeErrors(ErrorCode).defineErrors({
23
+ [ErrorCode.UnknownError]: {
24
+ status: '500',
25
+ description: 'Unknown Error',
26
+ responseValidator: unknownErrorResponseValidator,
27
+ },
28
+ [ErrorCode.ValidationFailed]: {
29
+ status: '500',
30
+ description: 'Validation Error',
31
+ responseValidator: validationErrorResponseValidator,
32
+ },
33
+ [ErrorCode.NotFound]: {
34
+ status: '500',
35
+ description: 'Not Found Error',
36
+ responseValidator: notFoundErrorResponseValidator,
37
+ },
38
+ }).defineDefaultError({
39
+ code: ErrorCode.UnknownError,
40
+ body: { error: ErrorCode.UnknownError },
41
+ }).customizeRoutes(SampleRouteType).defineRoutes({
42
+ [SampleRouteType.Public]: {
43
+ authorization: false,
44
+ errors: {
45
+ NotFound: true,
46
+ },
47
+ },
48
+ }).create();
49
+ const expectedPartial = `
50
+ "500":
51
+ description: Unknown Error or Not Found Error
52
+ content:
53
+ application/json:
54
+ schema:
55
+ oneOf:
56
+ - type: object
57
+ properties:
58
+ error:
59
+ type: string
60
+ const: UnknownError
61
+ description: Code to handle on the frontend
62
+ required:
63
+ - error
64
+ description: Error response
65
+ - type: object
66
+ properties:
67
+ error:
68
+ type: string
69
+ const: NotFound
70
+ description: Code to handle on the frontend
71
+ required:
72
+ - error
73
+ description: Error response`;
74
+ api.addRoute(api.factory.createRoute({
75
+ type: SampleRouteType.Public,
76
+ method: Method.GET,
77
+ path: '/',
78
+ description: 'Sample route',
79
+ validators: {
80
+ response: z.string().openapi({ description: 'Sample response' }),
81
+ },
82
+ handler: () => Promise.resolve('something'),
83
+ }));
84
+ const yml = api.schemaGenerator.getYaml().toString();
85
+ console.log(yml);
86
+ expect(yml).toContain(expectedPartial);
87
+ });
88
+ test('Poperly prints POST methods schema', () => {
89
+ const api = OpenApi.builder.customizeRoutes(SampleRouteType).defineRoutes({
90
+ [SampleRouteType.Public]: {
91
+ authorization: true,
92
+ },
93
+ }).create();
94
+ api.addRoute(api.factory.createRoute({
95
+ type: SampleRouteType.Public,
96
+ method: Method.POST,
97
+ path: '/',
98
+ description: 'My post method',
99
+ validators: {
100
+ body: z.object({
101
+ name: z.string().openapi({ description: 'Name of something' }),
102
+ }),
103
+ response: z.string().openapi({ description: 'Status' }),
104
+ },
105
+ handler: () => Promise.resolve('Hello'),
106
+ }));
107
+ const expectedPartial = `
108
+ /:
109
+ post:
110
+ description: My post method
111
+ security:
112
+ - bearerHttpAuthentication: []
113
+ requestBody:
114
+ content:
115
+ application/json:
116
+ schema:
117
+ type: object
118
+ properties:
119
+ name:
120
+ type: string
121
+ description: Name of something
122
+ required:
123
+ - name
124
+ responses:
125
+ "200":
126
+ description: Good Response
127
+ content:
128
+ application/json:
129
+ schema:
130
+ type: string
131
+ description: Status`;
132
+ const yml = api.schemaGenerator.getYaml().toString();
133
+ expect(yml).toContain(expectedPartial);
134
+ });
135
+ test('Can save schema to file', () => {
136
+ const api = TestUtils.createOpenApi();
137
+ expect(existsSync('/temp/schema.yml')).toBe(false);
138
+ api.schemaGenerator.saveYaml('temp/schema.yml');
139
+ readFileSync('temp/schema.yml');
140
+ expect(existsSync('temp/schema.yml')).toBe(true);
141
+ });
142
+ });
@@ -1,7 +1,7 @@
1
- import { OpenApi } from '../../OpenApi.js';
2
- import { AnyConfig } from '../../types/config/AnyConfig.js';
3
- import { RoutePath } from '../../types/RoutePath.js';
4
- import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
1
+ import { OpenApi } from '../../OpenApi';
2
+ import { AnyConfig } from '../../types/config/AnyConfig';
3
+ import { RoutePath } from '../../types/RoutePath';
4
+ import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils';
5
5
  export declare class TanstackStartWrapper<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
6
6
  protected service: OpenApi<TRouteTypes, TErrorCodes, TConfig>;
7
7
  protected developmentUtils: DevelopmentUtils;
@@ -1,4 +1,4 @@
1
- import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
1
+ import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils';
2
2
  export class TanstackStartWrapper {
3
3
  service;
4
4
  developmentUtils;
@@ -0,0 +1,44 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { TestUtils } from '../TestUtils/TestUtils';
3
+ import { ErrorCode } from '../../enums/ErrorCode';
4
+ describe('TanstackStartWrapper', () => {
5
+ test.skip('Can mount api on tanstack start', async () => {
6
+ });
7
+ describe('unit', () => {
8
+ test('Can mount the api correctly', async () => {
9
+ const api = TestUtils.createOpenApi();
10
+ const methods = api.wrappers.tanstackStart.getOpenApiRootMethods();
11
+ const errResponse = await methods.GET({ request: TestUtils.createRequest('/api') });
12
+ expect(errResponse.status, 'Should be 404 on unknown route').toBe(404);
13
+ expect(await errResponse.json(), 'Should be error on unknown route').toEqual({ error: ErrorCode.NotFound });
14
+ const goodResponse = await methods.GET({ request: TestUtils.createRequest('/sample') });
15
+ expect(goodResponse.status, 'Should be 200 on sample route').toBe(200);
16
+ expect(await goodResponse.json(), "Should be body 'success'").toEqual('success');
17
+ });
18
+ test('Can mount swagger routes correctly', async () => {
19
+ const api = TestUtils.createOpenApi();
20
+ const methods = api.wrappers.tanstackStart.createSwaggerMethods('/openapi-schema');
21
+ const goodResponse = await methods.GET();
22
+ expect(goodResponse.status, 'Should be 200 on swagger route').toBe(200);
23
+ const body = await goodResponse.text();
24
+ expect(body, 'Swagger UI pieces should be in HTML').toContain('swagger-ui');
25
+ expect(body, 'Default schema path should be present').toContain('/openapi-schema');
26
+ });
27
+ test('Can mount stoplight routes correctly', async () => {
28
+ const api = TestUtils.createOpenApi();
29
+ const methods = api.wrappers.tanstackStart.createStoplightMethods('/openapi-schema');
30
+ const goodResponse = await methods.GET();
31
+ expect(goodResponse.status, 'Should be 200 on stoplight route').toBe(200);
32
+ const body = await goodResponse.text();
33
+ expect(body, 'Stoplight UI pieces should be in HTML').toContain('@stoplight');
34
+ expect(body, 'Default schema path should be present').toContain('/openapi-schema');
35
+ });
36
+ test('Can mount schema routes correctly', async () => {
37
+ const api = TestUtils.createOpenApi();
38
+ const methods = api.wrappers.tanstackStart.createShemaMethods();
39
+ const goodResponse = await methods.GET();
40
+ expect(goodResponse.status, 'Should be 200 on schema route').toBe(200);
41
+ expect(await goodResponse.text(), 'Response should contain pieces of openapi schema').toContain('Sample route');
42
+ });
43
+ });
44
+ });
@@ -1,13 +1,13 @@
1
- import { OpenApi } from '../../OpenApi.js';
2
- import { Method } from '../../enums/Methods.js';
3
- import { SampleRouteType } from '../../enums/SampleRouteType.js';
4
- import { RoutePath } from '../../types/RoutePath.js';
1
+ import { OpenApi } from '../../OpenApi';
2
+ import { Method } from '../../enums/Methods';
3
+ import { SampleRouteType } from '../../enums/SampleRouteType';
4
+ import { RoutePath } from '../../types/RoutePath';
5
5
  export declare class TestUtils {
6
6
  static createRequest(route: RoutePath, method?: Method, body?: object): Request;
7
7
  static sendRequest(api: OpenApi<any, any, any>, route: RoutePath, method?: Method, body?: object): Promise<{
8
8
  status: number;
9
9
  body: any;
10
10
  }>;
11
- static createOpenApi(): OpenApi<SampleRouteType, import("../../index.js").OpenApiErrorCode, import("../../index.js").OpenApiDefaultConfig>;
11
+ static createOpenApi(): OpenApi<SampleRouteType, import("../..").OpenApiErrorCode, import("../..").OpenApiDefaultConfig>;
12
12
  static awaitGeneric<T>(timeoutMs: number, intervalMs: number, callback: () => Promise<T | null>): Promise<T | null>;
13
13
  }
@@ -1,7 +1,7 @@
1
1
  import z from 'zod';
2
- import { OpenApi } from '../../OpenApi.js';
3
- import { Method } from '../../enums/Methods.js';
4
- import { SampleRouteType } from '../../enums/SampleRouteType.js';
2
+ import { OpenApi } from '../../OpenApi';
3
+ import { Method } from '../../enums/Methods';
4
+ import { SampleRouteType } from '../../enums/SampleRouteType';
5
5
  export class TestUtils {
6
6
  static createRequest(route, method = Method.GET, body) {
7
7
  const request = new Request(`http://localhost${route}`, {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { TestUtils } from './TestUtils';
3
+ describe('TestUtils', () => {
4
+ test('Await generic fails on timeout', async () => {
5
+ const nullVal = await TestUtils.awaitGeneric(500, 50, async () => {
6
+ return null;
7
+ });
8
+ expect(nullVal).toBe(null);
9
+ });
10
+ test('Await generic fails on timeout', async () => {
11
+ let val = null;
12
+ setTimeout(() => {
13
+ val = true;
14
+ }, 300);
15
+ const gatheredVal = await TestUtils.awaitGeneric(1000, 50, async () => {
16
+ return val;
17
+ });
18
+ expect(val).toBe(gatheredVal);
19
+ });
20
+ });
@@ -1,8 +1,8 @@
1
1
  import 'zod-openapi/extend';
2
2
  import { array, number, object } from 'zod';
3
- import { stringDateTransformer } from './transformers/stringDateTransformer.js';
4
- import { stringNumberTransformer } from './transformers/stringNumberTransfromer.js';
5
- import { stringBooleanTransformer } from './transformers/stringBooleanTransformer.js';
3
+ import { stringDateTransformer } from './transformers/stringDateTransformer';
4
+ import { stringNumberTransformer } from './transformers/stringNumberTransfromer';
5
+ import { stringBooleanTransformer } from './transformers/stringBooleanTransformer';
6
6
  export class ValidationUtils {
7
7
  strings = {
8
8
  datetime: stringDateTransformer,
@@ -0,0 +1 @@
1
+ import 'zod-openapi/extend';
@@ -0,0 +1,165 @@
1
+ import 'zod-openapi/extend';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { ValidationUtils } from './ValidationUtils';
4
+ import { OpenApi } from '../../OpenApi';
5
+ import z from 'zod';
6
+ import { SampleRouteType } from '../../enums/SampleRouteType';
7
+ import { Method } from '../../enums/Methods';
8
+ import { TestUtils } from '../TestUtils/TestUtils';
9
+ describe('ValidationUtils', () => {
10
+ const utils = new ValidationUtils();
11
+ const api = OpenApi.builder.defineGlobalConfig({
12
+ basePath: '/api',
13
+ skipDescriptionsCheck: true,
14
+ }).create();
15
+ describe('Transformers', () => {
16
+ test('Correctly parses numbers from query', async () => {
17
+ const route = api.factory.createRoute({
18
+ type: SampleRouteType.Public,
19
+ method: Method.GET,
20
+ path: '/number',
21
+ description: 'Some description',
22
+ validators: {
23
+ query: z.object({
24
+ number: utils.strings.number.openapi({ description: 'test' }),
25
+ }).openapi({ description: 'response' }),
26
+ response: z.number().openapi({ description: 'test' }),
27
+ },
28
+ handler: (ctx) => Promise.resolve(ctx.params.query.number),
29
+ });
30
+ api.addRoute(route);
31
+ const req = TestUtils.createRequest('/api/number?number=10');
32
+ const res = await api.processRootRoute(req);
33
+ expect(res.status).toBe(200);
34
+ expect(res.body).toBe(10);
35
+ });
36
+ test('Correctly parses Dates from query', async () => {
37
+ const route = api.factory.createRoute({
38
+ type: SampleRouteType.Public,
39
+ method: Method.GET,
40
+ path: '/date',
41
+ description: 'Some description',
42
+ validators: {
43
+ query: z.object({
44
+ date: utils.strings.datetime.openapi({ description: 'test' }),
45
+ }).openapi({ description: 'response' }),
46
+ response: z.date().openapi({ description: 'test' }),
47
+ },
48
+ handler: (ctx) => Promise.resolve(ctx.params.query.date),
49
+ });
50
+ api.addRoute(route);
51
+ const req = TestUtils.createRequest('/api/date?date=2025-07-03T08:28:26.268Z');
52
+ const res = await api.processRootRoute(req);
53
+ expect(res.status).toBe(200);
54
+ expect(z.date().safeParse(res.body).data?.toISOString()).toBe('2025-07-03T08:28:26.268Z');
55
+ });
56
+ });
57
+ describe('Validators', () => {
58
+ test('Paginated Query', async () => {
59
+ const route = api.factory.createRoute({
60
+ type: SampleRouteType.Public,
61
+ method: Method.GET,
62
+ path: '/paginatedQuery',
63
+ description: 'Some description',
64
+ validators: {
65
+ query: utils.paginatedQuery({
66
+ name: z.string(),
67
+ date: utils.strings.datetime,
68
+ }),
69
+ response: z.object({
70
+ name: z.string(),
71
+ date: z.date(),
72
+ page: z.number().optional(),
73
+ pageSize: z.number().optional(),
74
+ }).openapi({ description: 'test' }),
75
+ },
76
+ handler: (ctx) => Promise.resolve(ctx.params.query),
77
+ });
78
+ api.addRoute(route);
79
+ const req = TestUtils.createRequest('/api/paginatedQuery?name=hello&date=2025-07-03T08:28:26.268Z');
80
+ const res = await api.processRootRoute(req);
81
+ expect(res.status).toBe(200);
82
+ expect(res.body).toStrictEqual({
83
+ page: 1,
84
+ name: 'hello',
85
+ date: new Date('2025-07-03T08:28:26.268Z'),
86
+ });
87
+ });
88
+ test('Paginated Response', async () => {
89
+ const route = api.factory.createRoute({
90
+ type: SampleRouteType.Public,
91
+ method: Method.GET,
92
+ path: '/paginatedResponse',
93
+ description: 'Some description',
94
+ validators: {
95
+ response: utils.paginatedResponse(z.object({
96
+ name: z.string(),
97
+ })),
98
+ },
99
+ handler: async () => {
100
+ const res = {
101
+ items: [{ name: 'John' }, { name: 'Snow' }],
102
+ info: {
103
+ count: 1000,
104
+ page: 10,
105
+ pageSize: 15,
106
+ },
107
+ };
108
+ return res;
109
+ },
110
+ });
111
+ api.addRoute(route);
112
+ const req = TestUtils.createRequest('/api/paginatedResponse');
113
+ const res = await api.processRootRoute(req);
114
+ expect(res.status).toBe(200);
115
+ expect(res.body).toStrictEqual({
116
+ items: [{ name: 'John' }, { name: 'Snow' }],
117
+ info: {
118
+ count: 1000,
119
+ page: 10,
120
+ pageSize: 15,
121
+ },
122
+ });
123
+ });
124
+ test('DescribeShape creates comprehensive descriptions for flat objects', async () => {
125
+ const api = OpenApi.builder.defineGlobalConfig({
126
+ basePath: '/api',
127
+ skipDescriptionsCheck: false,
128
+ }).create();
129
+ const validator = z.object({
130
+ name: z.string(),
131
+ age: z.number(),
132
+ }).openapi({ description: 'Test object' });
133
+ const route = api.factory.createRoute({
134
+ type: SampleRouteType.Public,
135
+ method: Method.GET,
136
+ path: '/test',
137
+ description: 'Test method',
138
+ validators: {
139
+ response: validator,
140
+ },
141
+ handler: async () => ({ name: 'Alex', age: 20 }),
142
+ });
143
+ expect(() => {
144
+ api.addRoute(route);
145
+ }).toThrowError(new Error("Route 'GET:/test': responseValidator missing openapi description on field 'name'"));
146
+ const updatedValidator = api.validators.describeShape(validator, {
147
+ name: 'Name of the object',
148
+ age: 'Age of the object',
149
+ }).openapi({ description: 'something' });
150
+ const route2 = api.factory.createRoute({
151
+ type: SampleRouteType.Public,
152
+ method: Method.GET,
153
+ path: '/test',
154
+ description: 'Test method',
155
+ validators: {
156
+ response: updatedValidator,
157
+ },
158
+ handler: async () => ({ name: 'Alex', age: 20 }),
159
+ });
160
+ expect(() => {
161
+ api.addRoute(route2);
162
+ }).not.toThrowError();
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { stringBooleanTransformer } from './stringBooleanTransformer';
3
+ describe('stringBooleanTransformer', () => {
4
+ test('Can parse true', async () => {
5
+ const result = stringBooleanTransformer.safeParse('true');
6
+ expect(result.success).toBe(true);
7
+ expect(result.data).toBe(true);
8
+ });
9
+ test('Can parse false', async () => {
10
+ const result = stringBooleanTransformer.safeParse('true');
11
+ expect(result.success).toBe(true);
12
+ expect(result.data).toBe(true);
13
+ });
14
+ test('Fails on non booleans', async () => {
15
+ const result = stringBooleanTransformer.safeParse('');
16
+ expect(result.success).toBe(false);
17
+ expect(result.error?.errors[0]?.message).toContain('Invalid enum value');
18
+ });
19
+ });