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.

Add async support for patch.input and patch() shorthand

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>

+185 -69
+1 -1
packages/openapi-python/src/createClient.ts
··· 100 100 } 101 101 102 102 const eventInputPatch = logger.timeEvent('input.patch'); 103 - patchOpenApiSpec({ patchOptions: config.parser.patch, spec: data }); 103 + await patchOpenApiSpec({ patchOptions: config.parser.patch, spec: data }); 104 104 eventInputPatch.timeEnd(); 105 105 106 106 const eventParser = logger.timeEvent('parser');
+1 -1
packages/openapi-ts/src/createClient.ts
··· 100 100 } 101 101 102 102 const eventInputPatch = logger.timeEvent('input.patch'); 103 - patchOpenApiSpec({ patchOptions: config.parser.patch, spec: data }); 103 + await patchOpenApiSpec({ patchOptions: config.parser.patch, spec: data }); 104 104 eventInputPatch.timeEnd(); 105 105 106 106 const eventParser = logger.timeEvent('parser');
+6 -2
packages/shared/src/config/parser/patch.ts
··· 10 10 OpenApiSchemaObject, 11 11 } from '../../openApi/types'; 12 12 13 + export type PatchInputFn = ( 14 + spec: OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X, 15 + ) => void | Promise<void>; 16 + 13 17 export type Patch = 14 - | ((spec: OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X) => void) 18 + | PatchInputFn 15 19 | { 16 20 /** 17 21 * Patch the raw OpenAPI spec object in place. Called before all other ··· 46 50 * } 47 51 * ``` 48 52 */ 49 - input?: (spec: OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X) => void; 53 + input?: PatchInputFn; 50 54 /** 51 55 * Patch the OpenAPI meta object in place. Useful for modifying general metadata such as title, description, version, or custom fields before further processing. 52 56 *
+174 -62
packages/shared/src/openApi/shared/utils/__tests__/patch.test.ts
··· 21 21 describe('patchOpenApiSpec', () => { 22 22 describe('patch.input', () => { 23 23 describe('OpenAPI v3', () => { 24 - it('calls patch.input function before other patches', () => { 24 + it('calls patch.input function before other patches', async () => { 25 25 const inputFn = vi.fn(); 26 26 const metaFn = vi.fn(); 27 27 const spec: OpenApi.V3_1_X = { 28 28 ...specMetadataV3, 29 29 }; 30 30 31 - patchOpenApiSpec({ 31 + await patchOpenApiSpec({ 32 32 patchOptions: { 33 33 input: inputFn, 34 34 meta: metaFn, ··· 42 42 expect(metaFn).toHaveBeenCalledOnce(); 43 43 }); 44 44 45 - it('allows bulk creation of component parameters', () => { 45 + it('allows bulk creation of component parameters', async () => { 46 46 const spec: OpenApi.V3_1_X = { 47 47 ...specMetadataV3, 48 48 paths: {}, 49 49 }; 50 50 51 - patchOpenApiSpec({ 51 + await patchOpenApiSpec({ 52 52 patchOptions: { 53 53 input: (spec) => { 54 54 if ('openapi' in spec) { ··· 72 72 }); 73 73 }); 74 74 75 - it('allows injecting parameters into multiple operations', () => { 75 + it('allows injecting parameters into multiple operations', async () => { 76 76 const spec: OpenApi.V3_1_X = { 77 77 ...specMetadataV3, 78 78 components: { ··· 103 103 } as any, 104 104 }; 105 105 106 - patchOpenApiSpec({ 106 + await patchOpenApiSpec({ 107 107 patchOptions: { 108 108 input: (spec) => { 109 109 // Inject parameter into all GET operations ··· 131 131 expect((spec.paths as any)['/baz'].post.parameters).toBeUndefined(); 132 132 }); 133 133 134 - it('allows complex Redfish-like transformations', () => { 134 + it('allows complex Redfish-like transformations', async () => { 135 135 const spec: OpenApi.V3_1_X = { 136 136 ...specMetadataV3, 137 137 paths: { ··· 158 158 { description: 'Select subset.', key: '$select' }, 159 159 ]; 160 160 161 - patchOpenApiSpec({ 161 + await patchOpenApiSpec({ 162 162 patchOptions: { 163 163 input: (spec) => { 164 164 if (!('openapi' in spec)) return; ··· 209 209 }); 210 210 211 211 describe('OpenAPI v2', () => { 212 - it('calls patch.input function for v2 specs', () => { 212 + it('calls patch.input function for v2 specs', async () => { 213 213 const inputFn = vi.fn(); 214 214 const spec: OpenApi.V2_0_X = { 215 215 ...specMetadataV2, 216 216 }; 217 217 218 - patchOpenApiSpec({ 218 + await patchOpenApiSpec({ 219 219 patchOptions: { 220 220 input: inputFn, 221 221 }, ··· 226 226 expect(inputFn).toHaveBeenCalledWith(spec); 227 227 }); 228 228 229 - it('allows adding definitions in v2 specs', () => { 229 + it('allows adding definitions in v2 specs', async () => { 230 230 const spec: OpenApi.V2_0_X = { 231 231 ...specMetadataV2, 232 232 }; 233 233 234 - patchOpenApiSpec({ 234 + await patchOpenApiSpec({ 235 235 patchOptions: { 236 236 input: (spec) => { 237 237 if ('swagger' in spec) { ··· 258 258 }); 259 259 }); 260 260 261 + describe('async patch support', () => { 262 + describe('patch.input async', () => { 263 + it('supports async patch.input function', async () => { 264 + const spec: OpenApi.V3_1_X = { 265 + ...specMetadataV3, 266 + }; 267 + 268 + let asyncExecuted = false; 269 + 270 + await patchOpenApiSpec({ 271 + patchOptions: { 272 + input: async (spec) => { 273 + await new Promise((resolve) => setTimeout(resolve, 10)); 274 + spec.info.title = 'Async Modified'; 275 + asyncExecuted = true; 276 + }, 277 + }, 278 + spec, 279 + }); 280 + 281 + expect(asyncExecuted).toBe(true); 282 + expect(spec.info.title).toBe('Async Modified'); 283 + }); 284 + 285 + it('supports async operations in patch.input', async () => { 286 + const spec: OpenApi.V3_1_X = { 287 + ...specMetadataV3, 288 + paths: {}, 289 + }; 290 + 291 + await patchOpenApiSpec({ 292 + patchOptions: { 293 + input: async (spec) => { 294 + // Simulate async operation like fetching data 295 + await new Promise((resolve) => setTimeout(resolve, 5)); 296 + if ('openapi' in spec) { 297 + if (!spec.components) spec.components = {}; 298 + if (!spec.components.parameters) spec.components.parameters = {}; 299 + spec.components.parameters.AsyncParam = { 300 + in: 'query', 301 + name: 'asyncParam', 302 + schema: { type: 'string' }, 303 + } as any; 304 + } 305 + }, 306 + }, 307 + spec, 308 + }); 309 + 310 + expect(spec.components?.parameters?.AsyncParam).toEqual({ 311 + in: 'query', 312 + name: 'asyncParam', 313 + schema: { type: 'string' }, 314 + }); 315 + }); 316 + }); 317 + 318 + describe('shorthand async', () => { 319 + it('supports async shorthand patch function', async () => { 320 + const spec: OpenApi.V3_1_X = { 321 + ...specMetadataV3, 322 + }; 323 + 324 + let asyncExecuted = false; 325 + 326 + await patchOpenApiSpec({ 327 + patchOptions: async (spec) => { 328 + await new Promise((resolve) => setTimeout(resolve, 10)); 329 + spec.info.title = 'Async Shorthand Modified'; 330 + asyncExecuted = true; 331 + }, 332 + spec, 333 + }); 334 + 335 + expect(asyncExecuted).toBe(true); 336 + expect(spec.info.title).toBe('Async Shorthand Modified'); 337 + }); 338 + 339 + it('supports async operations in shorthand function', async () => { 340 + const spec: OpenApi.V3_1_X = { 341 + ...specMetadataV3, 342 + components: { 343 + schemas: { 344 + Foo: { 345 + type: 'string', 346 + }, 347 + }, 348 + }, 349 + }; 350 + 351 + await patchOpenApiSpec({ 352 + patchOptions: async (spec) => { 353 + // Simulate async operation 354 + await new Promise((resolve) => setTimeout(resolve, 5)); 355 + spec.info.description = 'Added via async shorthand'; 356 + if ('openapi' in spec && spec.components?.schemas) { 357 + (spec.components.schemas as any).Bar = { 358 + type: 'number', 359 + }; 360 + } 361 + }, 362 + spec, 363 + }); 364 + 365 + expect(spec.info.description).toBe('Added via async shorthand'); 366 + expect((spec.components?.schemas as any)?.Bar).toEqual({ 367 + type: 'number', 368 + }); 369 + }); 370 + }); 371 + }); 372 + 261 373 describe('shorthand patch function', () => { 262 374 describe('OpenAPI v3', () => { 263 - it('calls shorthand patch function', () => { 375 + it('calls shorthand patch function', async () => { 264 376 const patchFn = vi.fn(); 265 377 const spec: OpenApi.V3_1_X = { 266 378 ...specMetadataV3, 267 379 }; 268 380 269 - patchOpenApiSpec({ 381 + await patchOpenApiSpec({ 270 382 patchOptions: patchFn, 271 383 spec, 272 384 }); ··· 275 387 expect(patchFn).toHaveBeenCalledWith(spec); 276 388 }); 277 389 278 - it('allows modifications through shorthand function', () => { 390 + it('allows modifications through shorthand function', async () => { 279 391 const spec: OpenApi.V3_1_X = { 280 392 ...specMetadataV3, 281 393 }; 282 394 283 - patchOpenApiSpec({ 395 + await patchOpenApiSpec({ 284 396 patchOptions: (spec) => { 285 397 spec.info.title = 'Modified Title'; 286 398 }, ··· 290 402 expect(spec.info.title).toBe('Modified Title'); 291 403 }); 292 404 293 - it('shorthand function replaces object-based patch configuration', () => { 405 + it('shorthand function replaces object-based patch configuration', async () => { 294 406 const spec: OpenApi.V3_1_X = { 295 407 ...specMetadataV3, 296 408 components: { ··· 304 416 305 417 // When using shorthand syntax, only the function is called 306 418 // Object properties like meta or schemas would be ignored 307 - patchOpenApiSpec({ 419 + await patchOpenApiSpec({ 308 420 patchOptions: (spec) => { 309 421 spec.info.title = 'Shorthand Title'; 310 422 // This is the only code that runs ··· 319 431 }); 320 432 321 433 describe('OpenAPI v2', () => { 322 - it('calls shorthand patch function for v2 specs', () => { 434 + it('calls shorthand patch function for v2 specs', async () => { 323 435 const patchFn = vi.fn(); 324 436 const spec: OpenApi.V2_0_X = { 325 437 ...specMetadataV2, 326 438 }; 327 439 328 - patchOpenApiSpec({ 440 + await patchOpenApiSpec({ 329 441 patchOptions: patchFn, 330 442 spec, 331 443 }); ··· 334 446 expect(patchFn).toHaveBeenCalledWith(spec); 335 447 }); 336 448 337 - it('allows modifications through shorthand function in v2', () => { 449 + it('allows modifications through shorthand function in v2', async () => { 338 450 const spec: OpenApi.V2_0_X = { 339 451 ...specMetadataV2, 340 452 }; 341 453 342 - patchOpenApiSpec({ 454 + await patchOpenApiSpec({ 343 455 patchOptions: (spec) => { 344 456 spec.info.title = 'Modified V2 Title'; 345 457 }, ··· 352 464 }); 353 465 354 466 describe('edge cases', () => { 355 - it('does not modify spec', () => { 467 + it('does not modify spec', async () => { 356 468 const spec: OpenApi.V3_1_X = { 357 469 ...specMetadataV3, 358 470 components: { ··· 364 476 }, 365 477 }; 366 478 367 - patchOpenApiSpec({ 479 + await patchOpenApiSpec({ 368 480 patchOptions: undefined, 369 481 spec, 370 482 }); ··· 381 493 }); 382 494 }); 383 495 384 - it('does not modify spec', () => { 496 + it('does not modify spec', async () => { 385 497 const spec: OpenApi.V3_1_X = { 386 498 ...specMetadataV3, 387 499 components: { ··· 393 505 }, 394 506 }; 395 507 396 - patchOpenApiSpec({ 508 + await patchOpenApiSpec({ 397 509 patchOptions: {}, 398 510 spec, 399 511 }); ··· 412 524 }); 413 525 414 526 describe('OpenAPI v3', () => { 415 - it('calls patch function', () => { 527 + it('calls patch function', async () => { 416 528 const fnBar = vi.fn(); 417 529 const fnFoo = vi.fn(); 418 530 ··· 430 542 }, 431 543 }; 432 544 433 - patchOpenApiSpec({ 545 + await patchOpenApiSpec({ 434 546 patchOptions: { 435 547 schemas: { 436 548 Bar: fnBar, ··· 450 562 }); 451 563 }); 452 564 453 - it('patch function mutates spec', () => { 565 + it('patch function mutates spec', async () => { 454 566 const spec: OpenApi.V3_1_X = { 455 567 ...specMetadataV3, 456 568 components: { ··· 494 606 }, 495 607 }; 496 608 497 - patchOpenApiSpec({ 609 + await patchOpenApiSpec({ 498 610 patchOptions: { 499 611 parameters: { 500 612 Foo: (schema) => { ··· 588 700 }); 589 701 }); 590 702 591 - it('handles spec without components', () => { 703 + it('handles spec without components', async () => { 592 704 const fn = vi.fn(); 593 705 594 706 const spec: OpenApi.V3_1_X = { 595 707 ...specMetadataV3, 596 708 }; 597 709 598 - patchOpenApiSpec({ 710 + await patchOpenApiSpec({ 599 711 patchOptions: { 600 712 parameters: { 601 713 Foo: fn, ··· 616 728 expect(fn).not.toHaveBeenCalled(); 617 729 }); 618 730 619 - it('handles spec without component namespaces', () => { 731 + it('handles spec without component namespaces', async () => { 620 732 const fn = vi.fn(); 621 733 622 734 const spec: OpenApi.V3_1_X = { ··· 624 736 components: {}, 625 737 }; 626 738 627 - patchOpenApiSpec({ 739 + await patchOpenApiSpec({ 628 740 patchOptions: { 629 741 parameters: { 630 742 Foo: fn, ··· 645 757 expect(fn).not.toHaveBeenCalled(); 646 758 }); 647 759 648 - it('handles spec without matching components', () => { 760 + it('handles spec without matching components', async () => { 649 761 const fn = vi.fn(); 650 762 651 763 const spec: OpenApi.V3_1_X = { ··· 658 770 }, 659 771 }; 660 772 661 - patchOpenApiSpec({ 773 + await patchOpenApiSpec({ 662 774 patchOptions: { 663 775 parameters: { 664 776 Foo: fn, ··· 679 791 expect(fn).not.toHaveBeenCalled(); 680 792 }); 681 793 682 - it('skips invalid schemas', () => { 794 + it('skips invalid schemas', async () => { 683 795 const fn = vi.fn(); 684 796 685 797 const spec: OpenApi.V3_1_X = { ··· 696 808 }, 697 809 }; 698 810 699 - patchOpenApiSpec({ 811 + await patchOpenApiSpec({ 700 812 patchOptions: { 701 813 schemas: { 702 814 Bar: fn, ··· 714 826 }); 715 827 }); 716 828 717 - it('applies meta patch function', () => { 829 + it('applies meta patch function', async () => { 718 830 const metaFn = vi.fn((meta) => { 719 831 meta.title = 'Changed Title'; 720 832 }); 721 833 const spec: OpenApi.V3_1_X = { 722 834 ...specMetadataV3, 723 835 }; 724 - patchOpenApiSpec({ 836 + await patchOpenApiSpec({ 725 837 patchOptions: { 726 838 meta: metaFn, 727 839 }, ··· 731 843 expect(spec.info.title).toBe('Changed Title'); 732 844 }); 733 845 734 - it('applies version patch function', () => { 846 + it('applies version patch function', async () => { 735 847 const versionFn = vi.fn((version) => `patched-${version}`); 736 848 const spec: OpenApi.V3_1_X = { 737 849 ...specMetadataV3, 738 850 }; 739 - patchOpenApiSpec({ 851 + await patchOpenApiSpec({ 740 852 patchOptions: { 741 853 version: versionFn, 742 854 }, ··· 748 860 }); 749 861 750 862 describe('OpenAPI v2', () => { 751 - it('calls patch function', () => { 863 + it('calls patch function', async () => { 752 864 const fnBar = vi.fn(); 753 865 const fnFoo = vi.fn(); 754 866 ··· 764 876 }, 765 877 }; 766 878 767 - patchOpenApiSpec({ 879 + await patchOpenApiSpec({ 768 880 patchOptions: { 769 881 schemas: { 770 882 Bar: fnBar, ··· 784 896 }); 785 897 }); 786 898 787 - it('patch function mutates schema', () => { 899 + it('patch function mutates schema', async () => { 788 900 const spec: OpenApi.V2_0_X = { 789 901 ...specMetadataV2, 790 902 definitions: { ··· 794 906 }, 795 907 }; 796 908 797 - patchOpenApiSpec({ 909 + await patchOpenApiSpec({ 798 910 patchOptions: { 799 911 schemas: { 800 912 Foo: (schema) => { ··· 815 927 }); 816 928 }); 817 929 818 - it('handles spec without definitions', () => { 930 + it('handles spec without definitions', async () => { 819 931 const fn = vi.fn(); 820 932 821 933 const spec: OpenApi.V2_0_X = { 822 934 ...specMetadataV2, 823 935 }; 824 936 825 - patchOpenApiSpec({ 937 + await patchOpenApiSpec({ 826 938 patchOptions: { 827 939 parameters: { 828 940 Foo: fn, ··· 843 955 expect(fn).not.toHaveBeenCalled(); 844 956 }); 845 957 846 - it('handles spec without matching definitions', () => { 958 + it('handles spec without matching definitions', async () => { 847 959 const fn = vi.fn(); 848 960 849 961 const spec: OpenApi.V2_0_X = { ··· 851 963 definitions: {}, 852 964 }; 853 965 854 - patchOpenApiSpec({ 966 + await patchOpenApiSpec({ 855 967 patchOptions: { 856 968 parameters: { 857 969 Foo: fn, ··· 872 984 expect(fn).not.toHaveBeenCalled(); 873 985 }); 874 986 875 - it('skips invalid schemas', () => { 987 + it('skips invalid schemas', async () => { 876 988 const fn = vi.fn(); 877 989 878 990 const spec: OpenApi.V2_0_X = { ··· 887 999 }, 888 1000 }; 889 1001 890 - patchOpenApiSpec({ 1002 + await patchOpenApiSpec({ 891 1003 patchOptions: { 892 1004 schemas: { 893 1005 Bar: fn, ··· 905 1017 }); 906 1018 }); 907 1019 908 - it('applies meta patch function', () => { 1020 + it('applies meta patch function', async () => { 909 1021 const metaFn = vi.fn((meta) => { 910 1022 meta.title = 'Changed Title'; 911 1023 }); 912 1024 const spec: OpenApi.V2_0_X = { 913 1025 ...specMetadataV2, 914 1026 }; 915 - patchOpenApiSpec({ 1027 + await patchOpenApiSpec({ 916 1028 patchOptions: { 917 1029 meta: metaFn, 918 1030 }, ··· 922 1034 expect(spec.info.title).toBe('Changed Title'); 923 1035 }); 924 1036 925 - it('applies version patch function', () => { 1037 + it('applies version patch function', async () => { 926 1038 const versionFn = vi.fn((version) => `patched-${version}`); 927 1039 const spec: OpenApi.V2_0_X = { 928 1040 ...specMetadataV2, 929 1041 }; 930 - patchOpenApiSpec({ 1042 + await patchOpenApiSpec({ 931 1043 patchOptions: { 932 1044 version: versionFn, 933 1045 }, ··· 939 1051 }); 940 1052 941 1053 describe('real-world usage', () => { 942 - it('handles complex schema example from docs', () => { 1054 + it('handles complex schema example from docs', async () => { 943 1055 const spec: OpenApi.V3_1_X = { 944 1056 ...specMetadataV3, 945 1057 components: { ··· 959 1071 }, 960 1072 }; 961 1073 962 - patchOpenApiSpec({ 1074 + await patchOpenApiSpec({ 963 1075 patchOptions: { 964 1076 schemas: { 965 1077 Foo: (schema: any) => { ··· 992 1104 }); 993 1105 }); 994 1106 995 - it('handles adding new schema properties', () => { 1107 + it('handles adding new schema properties', async () => { 996 1108 const spec: OpenApi.V3_1_X = { 997 1109 ...specMetadataV3, 998 1110 components: { ··· 1007 1119 }, 1008 1120 }; 1009 1121 1010 - patchOpenApiSpec({ 1122 + await patchOpenApiSpec({ 1011 1123 patchOptions: { 1012 1124 schemas: { 1013 1125 Foo: (schema: any) => { ··· 1042 1154 }); 1043 1155 }); 1044 1156 1045 - it('handles removing schema properties', () => { 1157 + it('handles removing schema properties', async () => { 1046 1158 const spec: OpenApi.V3_1_X = { 1047 1159 ...specMetadataV3, 1048 1160 components: { ··· 1059 1171 }, 1060 1172 }; 1061 1173 1062 - patchOpenApiSpec({ 1174 + await patchOpenApiSpec({ 1063 1175 patchOptions: { 1064 1176 schemas: { 1065 1177 Foo: (schema: any) => {
+3 -3
packages/shared/src/openApi/shared/utils/patch.ts
··· 1 1 import type { Patch } from '../../../config/parser/patch'; 2 2 import type { OpenApi } from '../../../openApi/types'; 3 3 4 - export function patchOpenApiSpec({ 4 + export async function patchOpenApiSpec({ 5 5 patchOptions, 6 6 spec: _spec, 7 7 }: { ··· 15 15 const spec = _spec as OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X; 16 16 17 17 if (typeof patchOptions === 'function') { 18 - patchOptions(spec); 18 + await patchOptions(spec); 19 19 return; 20 20 } 21 21 22 22 if (patchOptions.input) { 23 - patchOptions.input(spec); 23 + await patchOptions.input(spec); 24 24 } 25 25 26 26 if ('swagger' in spec) {