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 #2213 from hey-api/fix/validators-additional-properties-object

fix(validators): handle additional properties object when no other properties are defined

authored by

Lubos and committed by
GitHub
41280004 69665af4

+158 -202
+5
.changeset/soft-bulldogs-cover.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(validators): handle additional properties object when no other properties are defined
+5 -2
packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts
··· 157 157 /** 158 158 * This is a string dictionary 159 159 */ 160 - export const vDictionaryWithDictionary = v.object({}); 160 + export const vDictionaryWithDictionary = v.record(v.string(), v.object({})); 161 161 162 162 /** 163 163 * This is a complex dictionary 164 164 */ 165 - export const vDictionaryWithProperties = v.object({}); 165 + export const vDictionaryWithProperties = v.record(v.string(), v.object({ 166 + foo: v.optional(v.string()), 167 + bar: v.optional(v.string()) 168 + })); 166 169 167 170 /** 168 171 * This is a type-only model that defines Date as a string
+5 -2
packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/zod/default/zod.gen.ts
··· 157 157 /** 158 158 * This is a string dictionary 159 159 */ 160 - export const zDictionaryWithDictionary = z.object({}); 160 + export const zDictionaryWithDictionary = z.record(z.object({})); 161 161 162 162 /** 163 163 * This is a complex dictionary 164 164 */ 165 - export const zDictionaryWithProperties = z.object({}); 165 + export const zDictionaryWithProperties = z.record(z.object({ 166 + foo: z.string().optional(), 167 + bar: z.string().optional() 168 + })); 166 169 167 170 /** 168 171 * This is a type-only model that defines Date as a string
+5 -2
packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts
··· 206 206 /** 207 207 * This is a string dictionary 208 208 */ 209 - export const vDictionaryWithDictionary = v.object({}); 209 + export const vDictionaryWithDictionary = v.record(v.string(), v.object({})); 210 210 211 211 /** 212 212 * This is a complex dictionary 213 213 */ 214 - export const vDictionaryWithProperties = v.object({}); 214 + export const vDictionaryWithProperties = v.record(v.string(), v.object({ 215 + foo: v.optional(v.string()), 216 + bar: v.optional(v.string()) 217 + })); 215 218 216 219 /** 217 220 * This is a model with one number property
+5 -2
packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/zod/default/zod.gen.ts
··· 220 220 /** 221 221 * This is a string dictionary 222 222 */ 223 - export const zDictionaryWithDictionary = z.object({}); 223 + export const zDictionaryWithDictionary = z.record(z.object({})); 224 224 225 225 /** 226 226 * This is a complex dictionary 227 227 */ 228 - export const zDictionaryWithProperties = z.object({}); 228 + export const zDictionaryWithProperties = z.record(z.object({ 229 + foo: z.string().optional(), 230 + bar: z.string().optional() 231 + })); 229 232 230 233 /** 231 234 * This is a model with one number property
+5 -2
packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts
··· 209 209 /** 210 210 * This is a string dictionary 211 211 */ 212 - export const vDictionaryWithDictionary = v.object({}); 212 + export const vDictionaryWithDictionary = v.record(v.string(), v.object({})); 213 213 214 214 /** 215 215 * This is a complex dictionary 216 216 */ 217 - export const vDictionaryWithProperties = v.object({}); 217 + export const vDictionaryWithProperties = v.record(v.string(), v.object({ 218 + foo: v.optional(v.string()), 219 + bar: v.optional(v.string()) 220 + })); 218 221 219 222 /** 220 223 * This is a model with one number property
+5 -2
packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/zod/default/zod.gen.ts
··· 223 223 /** 224 224 * This is a string dictionary 225 225 */ 226 - export const zDictionaryWithDictionary = z.object({}); 226 + export const zDictionaryWithDictionary = z.record(z.object({})); 227 227 228 228 /** 229 229 * This is a complex dictionary 230 230 */ 231 - export const zDictionaryWithProperties = z.object({}); 231 + export const zDictionaryWithProperties = z.record(z.object({ 232 + foo: z.string().optional(), 233 + bar: z.string().optional() 234 + })); 232 235 233 236 /** 234 237 * This is a model with one number property
+4
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts
··· 28 28 29 29 export const vBaz = v.optional(v.pipe(v.pipe(v.string(), v.regex(/foo\nbar/)), v.readonly()), 'baz'); 30 30 31 + export const vQux = v.record(v.string(), v.object({ 32 + qux: v.optional(v.string()) 33 + })); 34 + 31 35 /** 32 36 * This is Foo parameter. 33 37 */
+4
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/zod.gen.ts
··· 28 28 29 29 export const zBaz = z.string().regex(/foo\nbar/).readonly().default('baz'); 30 30 31 + export const zQux = z.record(z.object({ 32 + qux: z.string().optional() 33 + })); 34 + 31 35 /** 32 36 * This is Foo parameter. 33 37 */
+4
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/valibot.gen.ts
··· 28 28 29 29 export const vBaz = v.optional(v.pipe(v.pipe(v.string(), v.regex(/foo\nbar/)), v.readonly()), 'baz'); 30 30 31 + export const vQux = v.record(v.string(), v.object({ 32 + qux: v.optional(v.string()) 33 + })); 34 + 31 35 /** 32 36 * This is Foo parameter. 33 37 */
+4
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/zod.gen.ts
··· 28 28 29 29 export const zBaz = z.string().regex(/foo\nbar/).readonly().default('baz'); 30 30 31 + export const zQux = z.record(z.object({ 32 + qux: z.string().optional() 33 + })); 34 + 31 35 /** 32 36 * This is Foo parameter. 33 37 */
+1 -1
packages/openapi-ts-tests/test/openapi-ts.config.ts
··· 158 158 { 159 159 // comments: false, 160 160 // exportFromIndex: true, 161 - // name: 'valibot', 161 + name: 'valibot', 162 162 }, 163 163 { 164 164 // case: 'snake_case',
+7
packages/openapi-ts-tests/test/spec/3.1.x/validators.yaml
··· 104 104 pattern: foo\nbar 105 105 readOnly: true 106 106 type: string 107 + Qux: 108 + additionalProperties: 109 + properties: 110 + qux: 111 + type: string 112 + type: object 113 + type: object
+76 -145
packages/openapi-ts/src/plugins/valibot/plugin.ts
··· 16 16 type: Extract<Required<IR.SchemaObject>['type'], T>; 17 17 } 18 18 19 - interface Result { 19 + interface State { 20 20 circularReferenceTracker: Set<string>; 21 21 hasCircularReference: boolean; 22 22 } ··· 39 39 }; 40 40 41 41 const arrayTypeToValibotSchema = ({ 42 - context, 43 42 plugin, 44 - result, 45 43 schema, 44 + state, 46 45 }: { 47 - context: IR.Context; 48 46 plugin: Plugin.Instance<Config>; 49 - result: Result; 50 47 schema: SchemaWithType<'array'>; 48 + state: State; 51 49 }): ts.CallExpression => { 52 50 const functionName = compiler.propertyAccessExpression({ 53 51 expression: identifiers.v, ··· 61 59 functionName, 62 60 parameters: [ 63 61 unknownTypeToValibotSchema({ 64 - context, 65 62 schema: { 66 63 type: 'unknown', 67 64 }, ··· 74 71 // at least one item is guaranteed 75 72 const itemExpressions = schema.items!.map((item) => { 76 73 const schemaPipes = schemaToValibotSchema({ 77 - context, 78 74 plugin, 79 - result, 80 75 schema: item, 76 + state, 81 77 }); 82 78 return pipesToExpression(schemaPipes); 83 79 }); ··· 102 98 functionName, 103 99 parameters: [ 104 100 unknownTypeToValibotSchema({ 105 - context, 106 101 schema: { 107 102 type: 'unknown', 108 103 }, ··· 148 143 const booleanTypeToValibotSchema = ({ 149 144 schema, 150 145 }: { 151 - context: IR.Context; 152 146 schema: SchemaWithType<'boolean'>; 153 147 }) => { 154 148 if (typeof schema.const === 'boolean') { ··· 172 166 }; 173 167 174 168 const enumTypeToValibotSchema = ({ 175 - context, 176 169 schema, 177 170 }: { 178 - context: IR.Context; 179 171 schema: SchemaWithType<'enum'>; 180 172 }): ts.CallExpression => { 181 173 const enumMembers: Array<ts.LiteralExpression> = []; ··· 197 189 198 190 if (!enumMembers.length) { 199 191 return unknownTypeToValibotSchema({ 200 - context, 201 192 schema: { 202 193 type: 'unknown', 203 194 }, ··· 234 225 // eslint-disable-next-line @typescript-eslint/no-unused-vars 235 226 schema, 236 227 }: { 237 - context: IR.Context; 238 228 schema: SchemaWithType<'never'>; 239 229 }) => { 240 230 const expression = compiler.callExpression({ ··· 250 240 // eslint-disable-next-line @typescript-eslint/no-unused-vars 251 241 schema, 252 242 }: { 253 - context: IR.Context; 254 243 schema: SchemaWithType<'null'>; 255 244 }) => { 256 245 const expression = compiler.callExpression({ ··· 290 279 const numberTypeToValibotSchema = ({ 291 280 schema, 292 281 }: { 293 - context: IR.Context; 294 282 schema: SchemaWithType<'integer' | 'number'>; 295 283 }) => { 296 284 const isBigInt = schema.type === 'integer' && schema.format === 'int64'; ··· 381 369 }; 382 370 383 371 const objectTypeToValibotSchema = ({ 384 - context, 385 372 plugin, 386 - result, 387 373 schema, 374 + state, 388 375 }: { 389 - context: IR.Context; 390 376 plugin: Plugin.Instance<Config>; 391 - result: Result; 392 377 schema: SchemaWithType<'object'>; 378 + state: State; 393 379 }): { 394 380 anyType: string; 395 381 expression: ts.CallExpression; ··· 397 383 // TODO: parser - handle constants 398 384 const properties: Array<ts.PropertyAssignment> = []; 399 385 400 - // let indexProperty: Property | undefined; 401 - // const schemaProperties: Array<Property> = []; 402 - // let indexPropertyItems: Array<IR.SchemaObject> = []; 403 386 const required = schema.required ?? []; 404 - // let hasOptionalProperties = false; 405 387 406 388 for (const name in schema.properties) { 407 389 const property = schema.properties[name]!; 408 390 const isRequired = required.includes(name); 409 391 410 392 const schemaPipes = schemaToValibotSchema({ 411 - context, 412 393 optional: !isRequired, 413 394 plugin, 414 - result, 415 395 schema: property, 396 + state, 416 397 }); 417 398 418 399 numberRegExp.lastIndex = 0; ··· 440 421 name: propertyName, 441 422 }), 442 423 ); 424 + } 443 425 444 - // indexPropertyItems.push(property); 445 - // if (!isRequired) { 446 - // hasOptionalProperties = true; 447 - // } 426 + if ( 427 + schema.additionalProperties && 428 + schema.additionalProperties.type === 'object' && 429 + !Object.keys(properties).length 430 + ) { 431 + const pipes = schemaToValibotSchema({ 432 + plugin, 433 + schema: schema.additionalProperties, 434 + state, 435 + }); 436 + const expression = compiler.callExpression({ 437 + functionName: compiler.propertyAccessExpression({ 438 + expression: identifiers.v, 439 + name: identifiers.schemas.record, 440 + }), 441 + parameters: [ 442 + compiler.callExpression({ 443 + functionName: compiler.propertyAccessExpression({ 444 + expression: identifiers.v, 445 + name: identifiers.schemas.string, 446 + }), 447 + parameters: [], 448 + }), 449 + pipesToExpression(pipes), 450 + ], 451 + }); 452 + return { 453 + anyType: 'AnyZodObject', 454 + expression, 455 + }; 448 456 } 449 457 450 - // if ( 451 - // schema.additionalProperties && 452 - // (schema.additionalProperties.type !== 'never' || !indexPropertyItems.length) 453 - // ) { 454 - // if (schema.additionalProperties.type === 'never') { 455 - // indexPropertyItems = [schema.additionalProperties]; 456 - // } else { 457 - // indexPropertyItems.unshift(schema.additionalProperties); 458 - // } 459 - 460 - // if (hasOptionalProperties) { 461 - // indexPropertyItems.push({ 462 - // type: 'undefined', 463 - // }); 464 - // } 465 - 466 - // indexProperty = { 467 - // isRequired: true, 468 - // name: 'key', 469 - // type: schemaToValibotSchema({ 470 - // context, 471 - // schema: 472 - // indexPropertyItems.length === 1 473 - // ? indexPropertyItems[0] 474 - // : { 475 - // items: indexPropertyItems, 476 - // logicalOperator: 'or', 477 - // }, 478 - // }), 479 - // }; 480 - // } 481 - 482 - // return compiler.typeInterfaceNode({ 483 - // indexProperty, 484 - // properties: schemaProperties, 485 - // useLegacyResolution: false, 486 - // }); 487 458 const expression = compiler.callExpression({ 488 459 functionName: compiler.propertyAccessExpression({ 489 460 expression: identifiers.v, ··· 501 472 const stringTypeToValibotSchema = ({ 502 473 schema, 503 474 }: { 504 - context: IR.Context; 505 475 schema: SchemaWithType<'string'>; 506 476 }) => { 507 477 if (typeof schema.const === 'string') { ··· 631 601 }; 632 602 633 603 const tupleTypeToValibotSchema = ({ 634 - context, 635 604 plugin, 636 - result, 637 605 schema, 606 + state, 638 607 }: { 639 - context: IR.Context; 640 608 plugin: Plugin.Instance<Config>; 641 - result: Result; 642 609 schema: SchemaWithType<'tuple'>; 610 + state: State; 643 611 }) => { 644 612 if (schema.const && Array.isArray(schema.const)) { 645 613 const tupleElements = schema.const.map((value) => ··· 668 636 if (schema.items) { 669 637 const tupleElements = schema.items.map((item) => { 670 638 const schemaPipes = schemaToValibotSchema({ 671 - context, 672 639 plugin, 673 - result, 674 640 schema: item, 641 + state, 675 642 }); 676 643 return pipesToExpression(schemaPipes); 677 644 }); ··· 690 657 } 691 658 692 659 return unknownTypeToValibotSchema({ 693 - context, 694 660 schema: { 695 661 type: 'unknown', 696 662 }, ··· 701 667 // eslint-disable-next-line @typescript-eslint/no-unused-vars 702 668 schema, 703 669 }: { 704 - context: IR.Context; 705 670 schema: SchemaWithType<'undefined'>; 706 671 }) => { 707 672 const expression = compiler.callExpression({ ··· 717 682 // eslint-disable-next-line @typescript-eslint/no-unused-vars 718 683 schema, 719 684 }: { 720 - context: IR.Context; 721 685 schema: SchemaWithType<'unknown'>; 722 686 }) => { 723 687 const expression = compiler.callExpression({ ··· 733 697 // eslint-disable-next-line @typescript-eslint/no-unused-vars 734 698 schema, 735 699 }: { 736 - context: IR.Context; 737 700 schema: SchemaWithType<'void'>; 738 701 }) => { 739 702 const expression = compiler.callExpression({ ··· 746 709 }; 747 710 748 711 const schemaTypeToValibotSchema = ({ 749 - context, 750 712 plugin, 751 - result, 752 713 schema, 714 + state, 753 715 }: { 754 - context: IR.Context; 755 716 plugin: Plugin.Instance<Config>; 756 - result: Result; 757 717 schema: IR.SchemaObject; 718 + state: State; 758 719 }): { 759 720 anyType?: string; 760 721 expression: ts.Expression; ··· 763 724 case 'array': 764 725 return { 765 726 expression: arrayTypeToValibotSchema({ 766 - context, 767 727 plugin, 768 - result, 769 728 schema: schema as SchemaWithType<'array'>, 729 + state, 770 730 }), 771 731 }; 772 732 case 'boolean': 773 733 return { 774 734 expression: booleanTypeToValibotSchema({ 775 - context, 776 735 schema: schema as SchemaWithType<'boolean'>, 777 736 }), 778 737 }; 779 738 case 'enum': 780 739 return { 781 740 expression: enumTypeToValibotSchema({ 782 - context, 783 741 schema: schema as SchemaWithType<'enum'>, 784 742 }), 785 743 }; ··· 787 745 case 'number': 788 746 return { 789 747 expression: numberTypeToValibotSchema({ 790 - context, 791 748 schema: schema as SchemaWithType<'integer' | 'number'>, 792 749 }), 793 750 }; 794 751 case 'never': 795 752 return { 796 753 expression: neverTypeToValibotSchema({ 797 - context, 798 754 schema: schema as SchemaWithType<'never'>, 799 755 }), 800 756 }; 801 757 case 'null': 802 758 return { 803 759 expression: nullTypeToValibotSchema({ 804 - context, 805 760 schema: schema as SchemaWithType<'null'>, 806 761 }), 807 762 }; 808 763 case 'object': 809 764 return objectTypeToValibotSchema({ 810 - context, 811 765 plugin, 812 - result, 813 766 schema: schema as SchemaWithType<'object'>, 767 + state, 814 768 }); 815 769 case 'string': 816 770 return { 817 771 expression: stringTypeToValibotSchema({ 818 - context, 819 772 schema: schema as SchemaWithType<'string'>, 820 773 }), 821 774 }; 822 775 case 'tuple': 823 776 return { 824 777 expression: tupleTypeToValibotSchema({ 825 - context, 826 778 plugin, 827 - result, 828 779 schema: schema as SchemaWithType<'tuple'>, 780 + state, 829 781 }), 830 782 }; 831 783 case 'undefined': 832 784 return { 833 785 expression: undefinedTypeToValibotSchema({ 834 - context, 835 786 schema: schema as SchemaWithType<'undefined'>, 836 787 }), 837 788 }; 838 789 case 'unknown': 839 790 return { 840 791 expression: unknownTypeToValibotSchema({ 841 - context, 842 792 schema: schema as SchemaWithType<'unknown'>, 843 793 }), 844 794 }; 845 795 case 'void': 846 796 return { 847 797 expression: voidTypeToValibotSchema({ 848 - context, 849 798 schema: schema as SchemaWithType<'void'>, 850 799 }), 851 800 }; ··· 853 802 }; 854 803 855 804 const operationToValibotSchema = ({ 856 - context, 857 805 operation, 858 806 plugin, 859 - result, 807 + state, 860 808 }: { 861 - context: IR.Context; 862 809 operation: IR.OperationObject; 863 810 plugin: Plugin.Instance<Config>; 864 - result: Result; 811 + state: State; 865 812 }) => { 866 813 if (operation.body) { 867 814 schemaToValibotSchema({ 868 815 $ref: operationIrRef({ 869 816 case: 'camelCase', 870 - config: context.config, 817 + config: plugin.context.config, 871 818 id: operation.id, 872 819 type: 'data', 873 820 }), 874 - context, 875 821 plugin, 876 - result, 877 822 schema: operation.body.schema, 823 + state, 878 824 }); 879 825 } 880 826 ··· 886 832 schemaToValibotSchema({ 887 833 $ref: operationIrRef({ 888 834 case: 'camelCase', 889 - config: context.config, 835 + config: plugin.context.config, 890 836 id: operation.id, 891 837 parameterId: parameter.name, 892 838 type: 'parameter', 893 839 }), 894 - context, 895 840 plugin, 896 - result, 897 841 schema: parameter.schema, 842 + state, 898 843 }); 899 844 } 900 845 } ··· 907 852 schemaToValibotSchema({ 908 853 $ref: operationIrRef({ 909 854 case: 'camelCase', 910 - config: context.config, 855 + config: plugin.context.config, 911 856 id: operation.id, 912 857 type: 'response', 913 858 }), 914 - context, 915 859 plugin, 916 - result, 917 860 schema: response, 861 + state, 918 862 }); 919 863 } 920 864 } ··· 922 866 923 867 const schemaToValibotSchema = ({ 924 868 $ref, 925 - context, 926 869 optional, 927 870 plugin, 928 - result, 929 871 schema, 872 + state, 930 873 }: { 931 874 /** 932 875 * When $ref is supplied, a node will be emitted to the file. 933 876 */ 934 877 $ref?: string; 935 - context: IR.Context; 936 878 /** 937 879 * Accept `optional` to handle optional object properties. We can't handle 938 880 * this inside the object function because `.optional()` must come before ··· 940 882 */ 941 883 optional?: boolean; 942 884 plugin: Plugin.Instance<Config>; 943 - result: Result; 944 885 schema: IR.SchemaObject; 886 + state: State; 945 887 }): Array<ts.Expression> => { 946 - const file = context.file({ id: valibotId })!; 888 + const file = plugin.context.file({ id: valibotId })!; 947 889 948 890 let anyType: string | undefined; 949 891 let identifier: ReturnType<typeof file.identifier> | undefined; 950 892 let pipes: Array<ts.Expression> = []; 951 893 952 894 if ($ref) { 953 - result.circularReferenceTracker.add($ref); 895 + state.circularReferenceTracker.add($ref); 954 896 955 897 identifier = file.identifier({ 956 898 $ref, ··· 961 903 } 962 904 963 905 if (schema.$ref) { 964 - const isCircularReference = result.circularReferenceTracker.has( 965 - schema.$ref, 966 - ); 906 + const isCircularReference = state.circularReferenceTracker.has(schema.$ref); 967 907 968 908 // if $ref hasn't been processed yet, inline it to avoid the 969 909 // "Block-scoped variable used before its declaration." error ··· 975 915 }); 976 916 977 917 if (!identifierRef.name) { 978 - const ref = context.resolveIrRef<IR.SchemaObject>(schema.$ref); 918 + const ref = plugin.context.resolveIrRef<IR.SchemaObject>(schema.$ref); 979 919 const schemaPipes = schemaToValibotSchema({ 980 920 $ref: schema.$ref, 981 - context, 982 921 plugin, 983 - result, 984 922 schema: ref, 923 + state, 985 924 }); 986 925 pipes.push(...schemaPipes); 987 926 ··· 1012 951 ], 1013 952 }); 1014 953 pipes.push(lazyExpression); 1015 - result.hasCircularReference = true; 954 + state.hasCircularReference = true; 1016 955 } else { 1017 956 pipes.push(refIdentifier); 1018 957 } 1019 958 } 1020 959 } else if (schema.type) { 1021 960 const valibotSchema = schemaTypeToValibotSchema({ 1022 - context, 1023 961 plugin, 1024 - result, 1025 962 schema, 963 + state, 1026 964 }); 1027 965 anyType = valibotSchema.anyType; 1028 966 pipes.push(valibotSchema.expression); ··· 1032 970 if (schema.items) { 1033 971 const itemTypes = schema.items.map((item) => { 1034 972 const schemaPipes = schemaToValibotSchema({ 1035 - context, 1036 973 plugin, 1037 - result, 1038 974 schema: item, 975 + state, 1039 976 }); 1040 977 return pipesToExpression(schemaPipes); 1041 978 }); ··· 1069 1006 } 1070 1007 } else { 1071 1008 const schemaPipes = schemaToValibotSchema({ 1072 - context, 1073 1009 plugin, 1074 - result, 1075 1010 schema, 1011 + state, 1076 1012 }); 1077 1013 pipes.push(...schemaPipes); 1078 1014 } 1079 1015 } else { 1080 1016 // catch-all fallback for failed schemas 1081 1017 const valibotSchema = schemaTypeToValibotSchema({ 1082 - context, 1083 1018 plugin, 1084 - result, 1085 1019 schema: { 1086 1020 type: 'unknown', 1087 1021 }, 1022 + state, 1088 1023 }); 1089 1024 anyType = valibotSchema.anyType; 1090 1025 pipes.push(valibotSchema.expression); 1091 1026 } 1092 1027 1093 1028 if ($ref) { 1094 - result.circularReferenceTracker.delete($ref); 1029 + state.circularReferenceTracker.delete($ref); 1095 1030 } 1096 1031 1097 1032 if (pipes.length) { ··· 1147 1082 exportConst: true, 1148 1083 expression: pipesToExpression(pipes), 1149 1084 name: identifier.name, 1150 - typeName: result.hasCircularReference 1085 + typeName: state.hasCircularReference 1151 1086 ? (compiler.propertyAccessExpression({ 1152 1087 expression: identifiers.v, 1153 1088 name: anyType || identifiers.types.GenericSchema.text, ··· 1176 1111 }); 1177 1112 1178 1113 plugin.subscribe('operation', ({ operation }) => { 1179 - const result: Result = { 1114 + const state: State = { 1180 1115 circularReferenceTracker: new Set(), 1181 1116 hasCircularReference: false, 1182 1117 }; 1183 1118 1184 1119 operationToValibotSchema({ 1185 - context: plugin.context, 1186 1120 operation, 1187 1121 plugin, 1188 - result, 1122 + state, 1189 1123 }); 1190 1124 }); 1191 1125 1192 1126 plugin.subscribe('parameter', ({ $ref, parameter }) => { 1193 - const result: Result = { 1127 + const state: State = { 1194 1128 circularReferenceTracker: new Set(), 1195 1129 hasCircularReference: false, 1196 1130 }; 1197 1131 1198 1132 schemaToValibotSchema({ 1199 1133 $ref, 1200 - context: plugin.context, 1201 1134 plugin, 1202 - result, 1203 1135 schema: parameter.schema, 1136 + state, 1204 1137 }); 1205 1138 }); 1206 1139 1207 1140 plugin.subscribe('requestBody', ({ $ref, requestBody }) => { 1208 - const result: Result = { 1141 + const state: State = { 1209 1142 circularReferenceTracker: new Set(), 1210 1143 hasCircularReference: false, 1211 1144 }; 1212 1145 1213 1146 schemaToValibotSchema({ 1214 1147 $ref, 1215 - context: plugin.context, 1216 1148 plugin, 1217 - result, 1218 1149 schema: requestBody.schema, 1150 + state, 1219 1151 }); 1220 1152 }); 1221 1153 1222 1154 plugin.subscribe('schema', ({ $ref, schema }) => { 1223 - const result: Result = { 1155 + const state: State = { 1224 1156 circularReferenceTracker: new Set(), 1225 1157 hasCircularReference: false, 1226 1158 }; 1227 1159 1228 1160 schemaToValibotSchema({ 1229 1161 $ref, 1230 - context: plugin.context, 1231 1162 plugin, 1232 - result, 1233 1163 schema, 1164 + state, 1234 1165 }); 1235 1166 }); 1236 1167 };
+23 -44
packages/openapi-ts/src/plugins/zod/plugin.ts
··· 41 41 const objectIdentifier = compiler.identifier({ text: 'object' }); 42 42 const optionalIdentifier = compiler.identifier({ text: 'optional' }); 43 43 const readonlyIdentifier = compiler.identifier({ text: 'readonly' }); 44 + const recordIdentifier = compiler.identifier({ text: 'record' }); 44 45 const regexIdentifier = compiler.identifier({ text: 'regex' }); 45 46 const unionIdentifier = compiler.identifier({ text: 'union' }); 46 47 const zIdentifier = compiler.identifier({ text: 'z' }); ··· 398 399 // TODO: parser - handle constants 399 400 const properties: Array<ts.PropertyAssignment> = []; 400 401 401 - // let indexProperty: Property | undefined; 402 - // const schemaProperties: Array<Property> = []; 403 - // let indexPropertyItems: Array<IR.SchemaObject> = []; 404 402 const required = schema.required ?? []; 405 - // let hasOptionalProperties = false; 406 403 407 404 for (const name in schema.properties) { 408 405 const property = schema.properties[name]!; ··· 440 437 name: propertyName, 441 438 }), 442 439 ); 443 - 444 - // indexPropertyItems.push(property); 445 - // if (!isRequired) { 446 - // hasOptionalProperties = true; 447 - // } 448 440 } 449 441 450 - // if ( 451 - // schema.additionalProperties && 452 - // (schema.additionalProperties.type !== 'never' || !indexPropertyItems.length) 453 - // ) { 454 - // if (schema.additionalProperties.type === 'never') { 455 - // indexPropertyItems = [schema.additionalProperties]; 456 - // } else { 457 - // indexPropertyItems.unshift(schema.additionalProperties); 458 - // } 459 - 460 - // if (hasOptionalProperties) { 461 - // indexPropertyItems.push({ 462 - // type: 'undefined', 463 - // }); 464 - // } 465 - 466 - // indexProperty = { 467 - // isRequired: true, 468 - // name: 'key', 469 - // type: schemaToZodSchema({ 470 - // schema: 471 - // indexPropertyItems.length === 1 472 - // ? indexPropertyItems[0] 473 - // : { 474 - // items: indexPropertyItems, 475 - // logicalOperator: 'or', 476 - // }, 477 - // }), 478 - // }; 479 - // } 442 + if ( 443 + schema.additionalProperties && 444 + schema.additionalProperties.type === 'object' && 445 + !Object.keys(properties).length 446 + ) { 447 + const zodSchema = schemaToZodSchema({ 448 + plugin, 449 + schema: schema.additionalProperties, 450 + state, 451 + }); 452 + const expression = compiler.callExpression({ 453 + functionName: compiler.propertyAccessExpression({ 454 + expression: zIdentifier, 455 + name: recordIdentifier, 456 + }), 457 + parameters: [zodSchema], 458 + }); 459 + return { 460 + anyType: 'AnyZodObject', 461 + expression, 462 + }; 463 + } 480 464 481 - // return compiler.typeInterfaceNode({ 482 - // indexProperty, 483 - // properties: schemaProperties, 484 - // useLegacyResolution: false, 485 - // }); 486 465 const expression = compiler.callExpression({ 487 466 functionName: compiler.propertyAccessExpression({ 488 467 expression: zIdentifier,