fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2117 from johnny-mh/add-fix-schema-plugin

authored by

Lubos and committed by
GitHub
7a2905b0 1cc9640b

+845 -1
+5
.changeset/sixty-teachers-play.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + feat: add `input.patch.schemas` feature
+5
.changeset/unlucky-houses-glow.md
··· 1 + --- 2 + '@docs/openapi-ts': patch 3 + --- 4 + 5 + docs: add docs for `input.patch.schemas` feature
+95
docs/openapi-ts/configuration.md
··· 474 474 }; 475 475 ``` 476 476 477 + ## Patch Schemas 478 + 479 + If you need to modify schemas in your OpenAPI specification before code generation, you can use `input.patch.schemas` to apply custom transformations to specific schemas. 480 + 481 + You can provide patch functions for individual schemas by their names. Each function receives the schema object and can modify it directly. 482 + 483 + ::: code-group 484 + 485 + ```js [date-time to timestamp] 486 + export default { 487 + input: { 488 + patch: { 489 + schemas: { 490 + UserResponseDto: (schema) => { 491 + // Convert date-time format to timestamp 492 + if (schema.properties?.updatedAt) { 493 + delete schema.properties.updatedAt.format; 494 + schema.properties.updatedAt.type = 'number'; 495 + } 496 + }, 497 + }, 498 + }, 499 + path: 'https://get.heyapi.dev/hey-api/backend', 500 + }, 501 + output: 'src/client', 502 + plugins: ['@hey-api/client-fetch'], 503 + }; 504 + ``` 505 + 506 + ```js [add properties] 507 + export default { 508 + input: { 509 + patch: { 510 + schemas: { 511 + ProductModel: (schema) => { 512 + // Add metadata property 513 + schema.properties.metadata = { 514 + type: 'object', 515 + additionalProperties: true, 516 + }; 517 + schema.required = ['id']; 518 + }, 519 + }, 520 + }, 521 + path: 'https://get.heyapi.dev/hey-api/backend', 522 + }, 523 + output: 'src/client', 524 + plugins: ['@hey-api/client-fetch'], 525 + }; 526 + ``` 527 + 528 + ```js [remove properties] 529 + export default { 530 + input: { 531 + patch: { 532 + schemas: { 533 + ApiResponseDto: (schema) => { 534 + // Remove internal fields 535 + delete schema.properties.internalField; 536 + }, 537 + }, 538 + }, 539 + path: 'https://get.heyapi.dev/hey-api/backend', 540 + }, 541 + output: 'src/client', 542 + plugins: ['@hey-api/client-fetch'], 543 + }; 544 + ``` 545 + 546 + ```ts [typescript] 547 + import { defineConfig, type OpenApiSchemaObject } from '@hey-api/openapi-ts'; 548 + 549 + export default defineConfig({ 550 + input: { 551 + patch: { 552 + schemas: { 553 + ApiResponseDto: (schema: OpenApiSchemaObject.V3_1_X) => { 554 + if (typeof schema.properties?.updatedAt === 'object') { 555 + delete schema.properties.updatedAt.format; 556 + schema.properties.updatedAt.type = 'number'; 557 + } 558 + }, 559 + }, 560 + }, 561 + path: 'https://get.heyapi.dev/hey-api/backend', 562 + }, 563 + output: 'src/client', 564 + plugins: ['@hey-api/client-fetch'], 565 + }); 566 + ``` 567 + 568 + ::: 569 + 570 + Patch functions work with both OpenAPI v3.x schemas (in `components.schemas`) and Swagger v2.0 schemas (in `definitions`). 571 + 477 572 ## Watch Mode 478 573 479 574 ::: warning
+620
packages/openapi-ts/src/__tests__/patchSchemas.test.ts
··· 1 + import { describe, expect, it, vi } from 'vitest'; 2 + 3 + import type { OpenApi } from '../openApi/types'; 4 + import { patchSchemas } from '../patchSchemas'; 5 + 6 + describe('patchSchemas', () => { 7 + describe('edge cases', () => { 8 + it('should return early when data is null', () => { 9 + const fixFn = vi.fn(); 10 + 11 + patchSchemas({ 12 + data: null, 13 + patch: { 14 + schemas: { 15 + TestSchema: fixFn, 16 + }, 17 + }, 18 + }); 19 + 20 + expect(fixFn).not.toHaveBeenCalled(); 21 + }); 22 + 23 + it('should return early when data is undefined', () => { 24 + const fixFn = vi.fn(); 25 + 26 + patchSchemas({ 27 + data: undefined, 28 + patch: { 29 + schemas: { 30 + TestSchema: fixFn, 31 + }, 32 + }, 33 + }); 34 + 35 + expect(fixFn).not.toHaveBeenCalled(); 36 + }); 37 + 38 + it('should return early when fix is undefined', () => { 39 + const mockSpec: OpenApi.V3_1_X = { 40 + components: { 41 + schemas: { 42 + TestSchema: { 43 + properties: { 44 + id: { type: 'string' }, 45 + }, 46 + type: 'object', 47 + }, 48 + }, 49 + }, 50 + info: { title: 'Test API', version: '1.0.0' }, 51 + openapi: '3.1.0', 52 + }; 53 + 54 + patchSchemas({ data: mockSpec }); 55 + 56 + expect(mockSpec.components?.schemas?.TestSchema).toEqual({ 57 + properties: { 58 + id: { type: 'string' }, 59 + }, 60 + type: 'object', 61 + }); 62 + }); 63 + 64 + it('should return early when fix.schema is undefined', () => { 65 + const fixFn = vi.fn(); 66 + const mockSpec: OpenApi.V3_1_X = { 67 + components: { 68 + schemas: { 69 + TestSchema: { 70 + properties: { 71 + id: { type: 'string' }, 72 + }, 73 + type: 'object', 74 + }, 75 + }, 76 + }, 77 + info: { title: 'Test API', version: '1.0.0' }, 78 + openapi: '3.1.0', 79 + }; 80 + 81 + patchSchemas({ 82 + data: mockSpec, 83 + patch: {}, 84 + }); 85 + 86 + expect(fixFn).not.toHaveBeenCalled(); 87 + }); 88 + }); 89 + 90 + describe('OpenAPI v3 schema fixing', () => { 91 + it('should apply fix function to matching schema in components.schemas', () => { 92 + const fixFn = vi.fn(); 93 + const mockSpec: OpenApi.V3_1_X = { 94 + components: { 95 + schemas: { 96 + TestSchema: { 97 + properties: { 98 + id: { type: 'string' }, 99 + updatedAt: { format: 'date-time', type: 'string' }, 100 + }, 101 + type: 'object', 102 + }, 103 + }, 104 + }, 105 + info: { title: 'Test API', version: '1.0.0' }, 106 + openapi: '3.1.0', 107 + }; 108 + 109 + patchSchemas({ 110 + data: mockSpec, 111 + patch: { 112 + schemas: { 113 + TestSchema: fixFn, 114 + }, 115 + }, 116 + }); 117 + 118 + expect(fixFn).toHaveBeenCalledWith({ 119 + properties: { 120 + id: { type: 'string' }, 121 + updatedAt: { format: 'date-time', type: 'string' }, 122 + }, 123 + type: 'object', 124 + }); 125 + }); 126 + 127 + it('should mutate schema object when fix function modifies it', () => { 128 + const mockSpec: OpenApi.V3_1_X = { 129 + components: { 130 + schemas: { 131 + TestSchema: { 132 + properties: { 133 + id: { type: 'string' }, 134 + updatedAt: { format: 'date-time', type: 'string' }, 135 + }, 136 + type: 'object', 137 + }, 138 + }, 139 + }, 140 + info: { title: 'Test API', version: '1.0.0' }, 141 + openapi: '3.1.0', 142 + }; 143 + 144 + patchSchemas({ 145 + data: mockSpec, 146 + patch: { 147 + schemas: { 148 + TestSchema: (schema: any) => { 149 + if (schema.properties?.updatedAt) { 150 + delete schema.properties.updatedAt.format; 151 + schema.properties.updatedAt.type = 'number'; 152 + } 153 + }, 154 + }, 155 + }, 156 + }); 157 + 158 + expect(mockSpec.components?.schemas?.TestSchema).toEqual({ 159 + properties: { 160 + id: { type: 'string' }, 161 + updatedAt: { type: 'number' }, 162 + }, 163 + type: 'object', 164 + }); 165 + }); 166 + 167 + it('should apply multiple fix functions to different schemas', () => { 168 + const fixFn1 = vi.fn(); 169 + const fixFn2 = vi.fn(); 170 + const mockSpec: OpenApi.V3_1_X = { 171 + components: { 172 + schemas: { 173 + ProductSchema: { 174 + properties: { name: { type: 'string' } }, 175 + type: 'object', 176 + }, 177 + UserSchema: { 178 + properties: { id: { type: 'string' } }, 179 + type: 'object', 180 + }, 181 + }, 182 + }, 183 + info: { title: 'Test API', version: '1.0.0' }, 184 + openapi: '3.1.0', 185 + }; 186 + 187 + patchSchemas({ 188 + data: mockSpec, 189 + patch: { 190 + schemas: { 191 + ProductSchema: fixFn2, 192 + UserSchema: fixFn1, 193 + }, 194 + }, 195 + }); 196 + 197 + expect(fixFn1).toHaveBeenCalledWith({ 198 + properties: { id: { type: 'string' } }, 199 + type: 'object', 200 + }); 201 + expect(fixFn2).toHaveBeenCalledWith({ 202 + properties: { name: { type: 'string' } }, 203 + type: 'object', 204 + }); 205 + }); 206 + 207 + it('should skip schemas that do not have matching fix functions', () => { 208 + const fixFn = vi.fn(); 209 + const mockSpec: OpenApi.V3_1_X = { 210 + components: { 211 + schemas: { 212 + TestSchema: { 213 + properties: { id: { type: 'string' } }, 214 + type: 'object', 215 + }, 216 + UnmatchedSchema: { 217 + properties: { name: { type: 'string' } }, 218 + type: 'object', 219 + }, 220 + }, 221 + }, 222 + info: { title: 'Test API', version: '1.0.0' }, 223 + openapi: '3.1.0', 224 + }; 225 + 226 + patchSchemas({ 227 + data: mockSpec, 228 + patch: { 229 + schemas: { 230 + TestSchema: fixFn, 231 + }, 232 + }, 233 + }); 234 + 235 + expect(fixFn).toHaveBeenCalledOnce(); 236 + expect(fixFn).toHaveBeenCalledWith({ 237 + properties: { id: { type: 'string' } }, 238 + type: 'object', 239 + }); 240 + }); 241 + 242 + it('should handle spec without components', () => { 243 + const fixFn = vi.fn(); 244 + const mockSpec: OpenApi.V3_1_X = { 245 + info: { title: 'Test API', version: '1.0.0' }, 246 + openapi: '3.1.0', 247 + }; 248 + 249 + patchSchemas({ 250 + data: mockSpec, 251 + patch: { 252 + schemas: { 253 + TestSchema: fixFn, 254 + }, 255 + }, 256 + }); 257 + 258 + expect(fixFn).not.toHaveBeenCalled(); 259 + }); 260 + 261 + it('should handle spec with components but no schemas', () => { 262 + const fixFn = vi.fn(); 263 + const mockSpec: OpenApi.V3_1_X = { 264 + components: {}, 265 + info: { title: 'Test API', version: '1.0.0' }, 266 + openapi: '3.1.0', 267 + }; 268 + 269 + patchSchemas({ 270 + data: mockSpec, 271 + patch: { 272 + schemas: { 273 + TestSchema: fixFn, 274 + }, 275 + }, 276 + }); 277 + 278 + expect(fixFn).not.toHaveBeenCalled(); 279 + }); 280 + 281 + it('should skip non-object schema values', () => { 282 + const fixFn = vi.fn(); 283 + const mockSpec: OpenApi.V3_1_X = { 284 + components: { 285 + schemas: { 286 + NumberSchema: 123 as any, 287 + StringSchema: 'invalid' as any, 288 + TestSchema: null as any, 289 + ValidSchema: { 290 + properties: { id: { type: 'string' } }, 291 + type: 'object', 292 + }, 293 + }, 294 + }, 295 + info: { title: 'Test API', version: '1.0.0' }, 296 + openapi: '3.1.0', 297 + }; 298 + 299 + patchSchemas({ 300 + data: mockSpec, 301 + patch: { 302 + schemas: { 303 + NumberSchema: fixFn, 304 + StringSchema: fixFn, 305 + TestSchema: fixFn, 306 + ValidSchema: fixFn, 307 + }, 308 + }, 309 + }); 310 + 311 + expect(fixFn).toHaveBeenCalledOnce(); 312 + expect(fixFn).toHaveBeenCalledWith({ 313 + properties: { id: { type: 'string' } }, 314 + type: 'object', 315 + }); 316 + }); 317 + }); 318 + 319 + describe('OpenAPI v2 (Swagger) schema fixing', () => { 320 + it('should apply fix function to matching schema in definitions', () => { 321 + const fixFn = vi.fn(); 322 + const mockSpec: OpenApi.V2_0_X = { 323 + definitions: { 324 + TestModel: { 325 + properties: { 326 + createdAt: { format: 'date-time', type: 'string' }, 327 + id: { type: 'string' }, 328 + }, 329 + type: 'object', 330 + }, 331 + }, 332 + info: { title: 'Test API', version: '1.0.0' }, 333 + paths: {}, 334 + swagger: '2.0', 335 + }; 336 + 337 + patchSchemas({ 338 + data: mockSpec, 339 + patch: { 340 + schemas: { 341 + TestModel: fixFn, 342 + }, 343 + }, 344 + }); 345 + 346 + expect(fixFn).toHaveBeenCalledWith({ 347 + properties: { 348 + createdAt: { format: 'date-time', type: 'string' }, 349 + id: { type: 'string' }, 350 + }, 351 + type: 'object', 352 + }); 353 + }); 354 + 355 + it('should mutate schema object in Swagger v2 spec', () => { 356 + const mockSpec: OpenApi.V2_0_X = { 357 + definitions: { 358 + TestModel: { 359 + properties: { 360 + id: { type: 'string' }, 361 + timestamp: { format: 'date-time', type: 'string' }, 362 + }, 363 + type: 'object', 364 + }, 365 + }, 366 + info: { title: 'Test API', version: '1.0.0' }, 367 + paths: {}, 368 + swagger: '2.0', 369 + }; 370 + 371 + patchSchemas({ 372 + data: mockSpec, 373 + patch: { 374 + schemas: { 375 + TestModel: (schema: any) => { 376 + if (schema.properties?.timestamp) { 377 + delete schema.properties.timestamp.format; 378 + schema.properties.timestamp.type = 'integer'; 379 + } 380 + }, 381 + }, 382 + }, 383 + }); 384 + 385 + expect(mockSpec.definitions?.TestModel).toEqual({ 386 + properties: { 387 + id: { type: 'string' }, 388 + timestamp: { type: 'integer' }, 389 + }, 390 + type: 'object', 391 + }); 392 + }); 393 + 394 + it('should handle Swagger spec without definitions', () => { 395 + const fixFn = vi.fn(); 396 + const mockSpec: OpenApi.V2_0_X = { 397 + info: { title: 'Test API', version: '1.0.0' }, 398 + paths: {}, 399 + swagger: '2.0', 400 + }; 401 + 402 + patchSchemas({ 403 + data: mockSpec, 404 + patch: { 405 + schemas: { 406 + TestModel: fixFn, 407 + }, 408 + }, 409 + }); 410 + 411 + expect(fixFn).not.toHaveBeenCalled(); 412 + }); 413 + 414 + it('should apply multiple fix functions in Swagger v2', () => { 415 + const fixFn1 = vi.fn(); 416 + const fixFn2 = vi.fn(); 417 + const mockSpec: OpenApi.V2_0_X = { 418 + definitions: { 419 + Product: { 420 + properties: { name: { type: 'string' } }, 421 + type: 'object', 422 + }, 423 + User: { 424 + properties: { id: { type: 'string' } }, 425 + type: 'object', 426 + }, 427 + }, 428 + info: { title: 'Test API', version: '1.0.0' }, 429 + paths: {}, 430 + swagger: '2.0', 431 + }; 432 + 433 + patchSchemas({ 434 + data: mockSpec, 435 + patch: { 436 + schemas: { 437 + Product: fixFn2, 438 + User: fixFn1, 439 + }, 440 + }, 441 + }); 442 + 443 + expect(fixFn1).toHaveBeenCalledWith({ 444 + properties: { id: { type: 'string' } }, 445 + type: 'object', 446 + }); 447 + expect(fixFn2).toHaveBeenCalledWith({ 448 + properties: { name: { type: 'string' } }, 449 + type: 'object', 450 + }); 451 + }); 452 + 453 + it('should skip non-object schema values in Swagger v2', () => { 454 + const fixFn = vi.fn(); 455 + const mockSpec: OpenApi.V2_0_X = { 456 + definitions: { 457 + InvalidModel: null as any, 458 + StringModel: 'invalid' as any, 459 + ValidModel: { 460 + properties: { id: { type: 'string' } }, 461 + type: 'object', 462 + }, 463 + }, 464 + info: { title: 'Test API', version: '1.0.0' }, 465 + paths: {}, 466 + swagger: '2.0', 467 + }; 468 + 469 + patchSchemas({ 470 + data: mockSpec, 471 + patch: { 472 + schemas: { 473 + InvalidModel: fixFn, 474 + StringModel: fixFn, 475 + ValidModel: fixFn, 476 + }, 477 + }, 478 + }); 479 + 480 + expect(fixFn).toHaveBeenCalledOnce(); 481 + expect(fixFn).toHaveBeenCalledWith({ 482 + properties: { id: { type: 'string' } }, 483 + type: 'object', 484 + }); 485 + }); 486 + }); 487 + 488 + describe('real-world usage scenarios', () => { 489 + it('should handle complex schema transformation like in config example', () => { 490 + const mockSpec: OpenApi.V3_1_X = { 491 + components: { 492 + schemas: { 493 + ChatAgentSkinItemAdminResponseDto: { 494 + properties: { 495 + id: { type: 'string' }, 496 + name: { type: 'string' }, 497 + updatedAt: { 498 + format: 'date-time', 499 + type: 'string', 500 + }, 501 + }, 502 + type: 'object', 503 + }, 504 + }, 505 + }, 506 + info: { title: 'Test API', version: '1.0.0' }, 507 + openapi: '3.1.0', 508 + }; 509 + 510 + patchSchemas({ 511 + data: mockSpec, 512 + patch: { 513 + schemas: { 514 + ChatAgentSkinItemAdminResponseDto: (schema: any) => { 515 + if (typeof schema.properties?.updatedAt === 'object') { 516 + delete schema.properties.updatedAt.format; 517 + schema.properties.updatedAt.type = 'number'; 518 + } 519 + }, 520 + }, 521 + }, 522 + }); 523 + 524 + expect( 525 + mockSpec.components?.schemas?.ChatAgentSkinItemAdminResponseDto, 526 + ).toEqual({ 527 + properties: { 528 + id: { type: 'string' }, 529 + name: { type: 'string' }, 530 + updatedAt: { 531 + type: 'number', 532 + }, 533 + }, 534 + type: 'object', 535 + }); 536 + }); 537 + 538 + it('should handle adding new properties to schema', () => { 539 + const mockSpec: OpenApi.V3_1_X = { 540 + components: { 541 + schemas: { 542 + UserModel: { 543 + properties: { 544 + id: { type: 'string' }, 545 + }, 546 + type: 'object', 547 + }, 548 + }, 549 + }, 550 + info: { title: 'Test API', version: '1.0.0' }, 551 + openapi: '3.1.0', 552 + }; 553 + 554 + patchSchemas({ 555 + data: mockSpec, 556 + patch: { 557 + schemas: { 558 + UserModel: (schema: any) => { 559 + schema.properties.metadata = { 560 + additionalProperties: true, 561 + type: 'object', 562 + }; 563 + schema.required = ['id']; 564 + }, 565 + }, 566 + }, 567 + }); 568 + 569 + expect(mockSpec.components?.schemas?.UserModel).toEqual({ 570 + properties: { 571 + id: { type: 'string' }, 572 + metadata: { 573 + additionalProperties: true, 574 + type: 'object', 575 + }, 576 + }, 577 + required: ['id'], 578 + type: 'object', 579 + }); 580 + }); 581 + 582 + it('should handle removing properties from schema', () => { 583 + const mockSpec: OpenApi.V3_1_X = { 584 + components: { 585 + schemas: { 586 + UserModel: { 587 + properties: { 588 + id: { type: 'string' }, 589 + internalField: { type: 'string' }, 590 + publicField: { type: 'string' }, 591 + }, 592 + type: 'object', 593 + }, 594 + }, 595 + }, 596 + info: { title: 'Test API', version: '1.0.0' }, 597 + openapi: '3.1.0', 598 + }; 599 + 600 + patchSchemas({ 601 + data: mockSpec, 602 + patch: { 603 + schemas: { 604 + UserModel: (schema: any) => { 605 + delete schema.properties.internalField; 606 + }, 607 + }, 608 + }, 609 + }); 610 + 611 + expect(mockSpec.components?.schemas?.UserModel).toEqual({ 612 + properties: { 613 + id: { type: 'string' }, 614 + publicField: { type: 'string' }, 615 + }, 616 + type: 'object', 617 + }); 618 + }); 619 + }); 620 + });
+7
packages/openapi-ts/src/createClient.ts
··· 6 6 import { getSpec } from './getSpec'; 7 7 import type { IR } from './ir/types'; 8 8 import { parseLegacy, parseOpenApiSpec } from './openApi'; 9 + import { patchSchemas } from './patchSchemas'; 9 10 import { processOutput } from './processOutput'; 10 11 import type { Client } from './types/client'; 11 12 import type { Config } from './types/config'; ··· 211 212 watch, 212 213 }); 213 214 Performance.end('spec'); 215 + 216 + if (config.input.patch) { 217 + Performance.start('patch'); 218 + patchSchemas({ data, patch: config.input.patch }); 219 + Performance.end('patch'); 220 + } 214 221 215 222 // throw on first run if there's an error to preserve user experience 216 223 // if in watch mode, subsequent errors won't throw to gracefully handle
+1 -1
packages/openapi-ts/src/index.ts
··· 99 99 100 100 export { defaultPlugins } from './initConfigs'; 101 101 export type { IR } from './ir/types'; 102 - export type { OpenApi } from './openApi/types'; 102 + export type { OpenApi, OpenApiSchemaObject } from './openApi/types'; 103 103 export { clientDefaultConfig } from './plugins/@hey-api/client-core/config'; 104 104 export { clientPluginHandler } from './plugins/@hey-api/client-core/plugin'; 105 105 export type { Client } from './plugins/@hey-api/client-core/types';
+11
packages/openapi-ts/src/openApi/types.d.ts
··· 1 1 import type { OpenApiV2_0_X } from './2.0.x'; 2 + import type { SchemaObject as OpenApiV2SchemaObject } from './2.0.x/types/spec'; 2 3 import type { OpenApiV3_0_X } from './3.0.x'; 4 + import type { SchemaObject as OpenApiV3SchemaObject } from './3.0.x/types/spec'; 3 5 import type { OpenApiV3_1_X } from './3.1.x'; 6 + import type { SchemaObject as OpenApiV3_1SchemaObject } from './3.1.x/types/spec'; 4 7 5 8 export namespace OpenApi { 6 9 export type V2_0_X = OpenApiV2_0_X; ··· 9 12 10 13 export type V3_1_X = OpenApiV3_1_X; 11 14 } 15 + 16 + export namespace OpenApiSchemaObject { 17 + export type V2_0_X = OpenApiV2SchemaObject; 18 + 19 + export type V3_0_X = OpenApiV3SchemaObject; 20 + 21 + export type V3_1_X = OpenApiV3_1SchemaObject; 22 + }
+50
packages/openapi-ts/src/patchSchemas.ts
··· 1 + import type { OpenApi } from './openApi/types'; 2 + import type { Config } from './types/config'; 3 + 4 + interface PatchSchemasArgs { 5 + data: unknown; 6 + patch?: Config['input']['patch']; 7 + } 8 + 9 + export const patchSchemas = ({ data, patch }: PatchSchemasArgs) => { 10 + if (!data || !patch?.schemas) { 11 + return; 12 + } 13 + 14 + const spec = data as OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X; 15 + const schemas = patch.schemas; 16 + 17 + const processSchemas = (targets: Record<string, any>) => { 18 + for (const schemaName in schemas) { 19 + const fixFn = schemas[schemaName]; 20 + 21 + if (!fixFn) { 22 + continue; 23 + } 24 + 25 + const target = targets[schemaName]; 26 + 27 + if (!target || typeof target !== 'object') { 28 + continue; 29 + } 30 + 31 + fixFn(target); 32 + } 33 + }; 34 + 35 + if ('swagger' in spec) { 36 + const { definitions } = spec; 37 + 38 + if (definitions) { 39 + processSchemas(definitions); 40 + } 41 + 42 + return; 43 + } 44 + 45 + const _schemas = spec.components?.schemas; 46 + 47 + if (_schemas) { 48 + processSchemas(_schemas); 49 + } 50 + };
+51
packages/openapi-ts/src/types/config.d.ts
··· 1 + import type { OpenApiSchemaObject } from '../openApi/types'; 1 2 import type { ClientPlugins, UserPlugins } from '../plugins'; 2 3 import type { ArrayOfObjectsToObjectMap, ExtractArrayOfObjects } from './utils'; 3 4 ··· 215 216 * @default ['after', 'before', 'cursor', 'offset', 'page', 'start'] 216 217 */ 217 218 keywords?: ReadonlyArray<string>; 219 + }; 220 + patch?: { 221 + /** 222 + * Apply custom transformations to schemas after parsing the OpenAPI specification. 223 + * This allows you to modify, fix, or enhance schema definitions before code generation. 224 + * 225 + * Each function receives the schema object and can modify it in place. Common use cases 226 + * include fixing incorrect data types, removing unwanted properties, adding missing 227 + * fields, or standardizing date/time formats. 228 + * 229 + * Works with both OpenAPI v2 (Swagger) and v3.x specifications, automatically 230 + * detecting the correct schema location (`definitions` for v2, `components.schemas` for v3). 231 + * 232 + * @example 233 + * ```js 234 + * patch: { 235 + * schemas: { 236 + * // Fix date-time format issues 237 + * 'UserResponseDto': (schema) => { 238 + * if (schema.properties?.updatedAt) { 239 + * delete schema.properties.updatedAt.format; 240 + * schema.properties.updatedAt.type = 'number'; 241 + * } 242 + * }, 243 + * // Add missing required fields 244 + * 'ProductModel': (schema) => { 245 + * schema.required = ['id', 'name']; 246 + * schema.properties.metadata = { 247 + * type: 'object', 248 + * additionalProperties: true 249 + * }; 250 + * }, 251 + * // Remove internal fields 252 + * 'PublicApiModel': (schema) => { 253 + * delete schema.properties.internalId; 254 + * delete schema.properties.secretKey; 255 + * } 256 + * } 257 + * } 258 + * ``` 259 + */ 260 + schemas?: Record< 261 + string, 262 + ( 263 + schema: 264 + | OpenApiSchemaObject.V2_0_X 265 + | OpenApiSchemaObject.V3_0_X 266 + | OpenApiSchemaObject.V3_1_X, 267 + ) => void 268 + >; 218 269 }; 219 270 /** 220 271 * Path to the OpenAPI specification. This can be either local or remote path.