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 #1177 from hey-api/refactor/parser-polish

refactor: polish parser output

authored by

Lubos and committed by
GitHub
11480ccc ef44e646

+237 -112
+66 -43
packages/openapi-ts/src/generate/types.ts
··· 834 834 ); 835 835 } 836 836 837 - return compiler.typeArrayNode( 837 + schema = deduplicateSchema({ schema }); 838 + 839 + // at least one item is guaranteed 840 + const itemTypes = schema.items!.map((item) => 838 841 schemaToType({ 839 842 context, 840 843 namespace, 841 - schema: { 842 - ...schema, 843 - type: undefined, 844 - }, 844 + schema: item, 845 845 }), 846 846 ); 847 + 848 + if (itemTypes.length === 1) { 849 + return compiler.typeArrayNode(itemTypes[0]); 850 + } 851 + 852 + if (schema.logicalOperator === 'and') { 853 + return compiler.typeArrayNode( 854 + compiler.typeIntersectionNode({ types: itemTypes }), 855 + ); 856 + } 857 + 858 + return compiler.typeArrayNode(compiler.typeUnionNode({ types: itemTypes })); 847 859 }; 848 860 849 861 const booleanTypeToIdentifier = ({ ··· 1020 1032 type: schemaToType({ 1021 1033 context, 1022 1034 namespace, 1023 - schema: { 1024 - items: indexPropertyItems, 1025 - logicalOperator: 'or', 1026 - }, 1035 + schema: 1036 + indexPropertyItems.length === 1 1037 + ? indexPropertyItems[0] 1038 + : { 1039 + items: indexPropertyItems, 1040 + logicalOperator: 'or', 1041 + }, 1027 1042 }), 1028 1043 }; 1029 1044 } ··· 1167 1182 /** 1168 1183 * Ensure we don't produce redundant types, e.g. string | string. 1169 1184 */ 1170 - const deduplicateSchema = ({ 1185 + const deduplicateSchema = <T extends IRSchemaObject>({ 1171 1186 schema, 1172 1187 }: { 1173 - schema: IRSchemaObject; 1174 - }): IRSchemaObject => { 1188 + schema: T; 1189 + }): T => { 1175 1190 if (!schema.items) { 1176 1191 return schema; 1177 1192 } ··· 1182 1197 for (const item of schema.items) { 1183 1198 // skip nested schemas for now, handle if necessary 1184 1199 if ( 1200 + !item.type || 1185 1201 item.type === 'boolean' || 1186 1202 item.type === 'null' || 1187 1203 item.type === 'number' || ··· 1189 1205 item.type === 'unknown' || 1190 1206 item.type === 'void' 1191 1207 ) { 1192 - const typeId = `${item.$ref ?? ''}${item.type ?? ''}${item.const ?? ''}`; 1208 + // const needs namespace to handle empty string values, otherwise 1209 + // fallback would equal an actual value and we would skip an item 1210 + const typeId = `${item.$ref ?? ''}${item.type ?? ''}${item.const !== undefined ? `const-${item.const}` : ''}`; 1193 1211 if (!typeIds.includes(typeId)) { 1194 1212 typeIds.push(typeId); 1195 1213 uniqueItems.push(item); ··· 1220 1238 1221 1239 // exclude unknown if it's the only type left 1222 1240 if (schema.type === 'unknown') { 1223 - return {}; 1241 + return {} as T; 1224 1242 } 1225 1243 1226 1244 return schema; ··· 1379 1397 return; 1380 1398 } 1381 1399 1382 - const errors: IRSchemaObject = {}; 1400 + let errors: IRSchemaObject = {}; 1383 1401 const errorsItems: Array<IRSchemaObject> = []; 1384 1402 1385 - const responses: IRSchemaObject = {}; 1403 + let responses: IRSchemaObject = {}; 1386 1404 const responsesItems: Array<IRSchemaObject> = []; 1387 1405 1388 1406 let defaultResponse: IRResponseObject | undefined; ··· 1452 1470 } 1453 1471 } 1454 1472 1455 - addItemsToSchema({ 1456 - items: errorsItems, 1457 - schema: errors, 1458 - }); 1459 - 1460 - addItemsToSchema({ 1461 - items: responsesItems, 1462 - schema: responses, 1463 - }); 1464 - 1465 - if (errors.items) { 1466 - const deduplicatedSchema = deduplicateSchema({ 1473 + if (errorsItems.length) { 1474 + errors = addItemsToSchema({ 1475 + items: errorsItems, 1476 + mutateSchemaOneItem: true, 1467 1477 schema: errors, 1468 1478 }); 1469 - if (Object.keys(deduplicatedSchema).length) { 1479 + errors = deduplicateSchema({ schema: errors }); 1480 + if (Object.keys(errors).length) { 1470 1481 const identifier = context.file({ id: typesId })!.identifier({ 1471 1482 $ref: operationErrorRef({ id: operation.id }), 1472 1483 create: true, ··· 1477 1488 name: identifier.name, 1478 1489 type: schemaToType({ 1479 1490 context, 1480 - schema: deduplicatedSchema, 1491 + schema: errors, 1481 1492 }), 1482 1493 }); 1483 1494 context.file({ id: typesId })!.add(node); 1484 1495 } 1485 1496 } 1486 1497 1487 - if (responses.items) { 1488 - const deduplicatedSchema = deduplicateSchema({ 1498 + if (responsesItems.length) { 1499 + responses = addItemsToSchema({ 1500 + items: responsesItems, 1501 + mutateSchemaOneItem: true, 1489 1502 schema: responses, 1490 1503 }); 1491 - if (Object.keys(deduplicatedSchema).length) { 1504 + responses = deduplicateSchema({ schema: responses }); 1505 + if (Object.keys(responses).length) { 1492 1506 const identifier = context.file({ id: typesId })!.identifier({ 1493 1507 $ref: operationResponseRef({ id: operation.id }), 1494 1508 create: true, ··· 1499 1513 name: identifier.name, 1500 1514 type: schemaToType({ 1501 1515 context, 1502 - schema: deduplicatedSchema, 1516 + schema: responses, 1503 1517 }), 1504 1518 }); 1505 1519 context.file({ id: typesId })!.add(node); ··· 1555 1569 schema, 1556 1570 }); 1557 1571 } else if (schema.items) { 1558 - const itemTypes = schema.items.map((item) => 1559 - schemaToType({ 1572 + schema = deduplicateSchema({ schema }); 1573 + if (schema.items) { 1574 + const itemTypes = schema.items.map((item) => 1575 + schemaToType({ 1576 + context, 1577 + namespace, 1578 + schema: item, 1579 + }), 1580 + ); 1581 + type = 1582 + schema.logicalOperator === 'and' 1583 + ? compiler.typeIntersectionNode({ types: itemTypes }) 1584 + : compiler.typeUnionNode({ types: itemTypes }); 1585 + } else { 1586 + type = schemaToType({ 1560 1587 context, 1561 1588 namespace, 1562 - schema: item, 1563 - }), 1564 - ); 1565 - type = 1566 - schema.logicalOperator === 'and' 1567 - ? compiler.typeIntersectionNode({ types: itemTypes }) 1568 - : compiler.typeUnionNode({ types: itemTypes }); 1589 + schema, 1590 + }); 1591 + } 1569 1592 } else { 1570 1593 // catch-all fallback for failed schemas 1571 1594 type = schemaTypeToIdentifier({
+24 -5
packages/openapi-ts/src/ir/utils.ts
··· 6 6 */ 7 7 export const addItemsToSchema = ({ 8 8 items, 9 + logicalOperator = 'or', 10 + mutateSchemaOneItem = false, 9 11 schema, 10 12 }: { 11 13 items: Array<IRSchemaObject>; 14 + logicalOperator?: IRSchemaObject['logicalOperator']; 15 + mutateSchemaOneItem?: boolean; 12 16 schema: IRSchemaObject; 13 17 }) => { 14 18 if (!items.length) { 15 - return; 19 + return schema; 20 + } 21 + 22 + if (schema.type === 'tuple') { 23 + schema.items = items; 24 + return schema; 16 25 } 17 26 18 - schema.items = items; 27 + if (items.length !== 1) { 28 + schema.items = items; 29 + schema.logicalOperator = logicalOperator; 30 + return schema; 31 + } 19 32 20 - if (items.length === 1 || schema.type === 'tuple') { 21 - return; 33 + if (mutateSchemaOneItem) { 34 + // bring composition up to avoid extraneous brackets 35 + schema = { 36 + ...schema, 37 + ...items[0], 38 + }; 39 + return schema; 22 40 } 23 41 24 - schema.logicalOperator = 'or'; 42 + schema.items = items; 43 + return schema; 25 44 };
+14 -10
packages/openapi-ts/src/openApi/3.1.0/parser/schema.ts
··· 135 135 } 136 136 } 137 137 138 - addItemsToSchema({ 138 + irSchema = addItemsToSchema({ 139 139 items: schemaItems, 140 140 schema: irSchema, 141 141 }); ··· 348 348 } 349 349 } 350 350 351 - if (schemaItems.length) { 352 - irSchema.items = schemaItems; 353 - irSchema.logicalOperator = 'and'; 354 - } 351 + irSchema = addItemsToSchema({ 352 + items: schemaItems, 353 + logicalOperator: 'and', 354 + mutateSchemaOneItem: true, 355 + schema: irSchema, 356 + }); 355 357 356 358 if (schemaTypes.includes('null')) { 357 359 // nest composition to avoid producing an intersection with null ··· 404 406 schemaItems.push({ type: 'null' }); 405 407 } 406 408 407 - addItemsToSchema({ 409 + irSchema = addItemsToSchema({ 408 410 items: schemaItems, 411 + mutateSchemaOneItem: true, 409 412 schema: irSchema, 410 413 }); 411 414 ··· 442 445 context: IRContext; 443 446 schema: SchemaWithRequired<'enum'>; 444 447 }): IRSchemaObject => { 445 - const irSchema = initIrSchema({ schema }); 448 + let irSchema = initIrSchema({ schema }); 446 449 447 450 irSchema.type = 'enum'; 448 451 ··· 477 480 } 478 481 } 479 482 480 - addItemsToSchema({ 483 + irSchema = addItemsToSchema({ 481 484 items: schemaItems, 482 485 schema: irSchema, 483 486 }); ··· 517 520 schemaItems.push({ type: 'null' }); 518 521 } 519 522 520 - addItemsToSchema({ 523 + irSchema = addItemsToSchema({ 521 524 items: schemaItems, 525 + mutateSchemaOneItem: true, 522 526 schema: irSchema, 523 527 }); 524 528 ··· 659 663 ); 660 664 } 661 665 662 - addItemsToSchema({ 666 + irSchema = addItemsToSchema({ 663 667 items: schemaItems, 664 668 schema: irSchema, 665 669 });
+23
packages/openapi-ts/test/3.1.0.spec.ts
··· 18 18 describe(`OpenAPI ${VERSION}`, () => { 19 19 const createConfig = (userConfig: UserConfig): UserConfig => ({ 20 20 client: '@hey-api/client-fetch', 21 + experimental_parser: true, 21 22 schemas: false, 22 23 ...userConfig, 23 24 input: path.join( ··· 42 43 }, 43 44 }), 44 45 description: 'does not generate duplicate null', 46 + }, 47 + { 48 + config: createConfig({ 49 + input: 'object-properties-all-of.json', 50 + output: 'object-properties-all-of', 51 + services: { 52 + export: false, 53 + }, 54 + }), 55 + description: 56 + 'sets correct logical operator and brackets on object with properties and allOf composition', 45 57 }, 46 58 { 47 59 config: createConfig({ ··· 53 65 }), 54 66 description: 55 67 'sets correct logical operator and brackets on object with properties and anyOf composition', 68 + }, 69 + { 70 + config: createConfig({ 71 + input: 'object-properties-one-of.json', 72 + output: 'object-properties-one-of', 73 + services: { 74 + export: false, 75 + }, 76 + }), 77 + description: 78 + 'sets correct logical operator and brackets on object with properties and oneOf composition', 56 79 }, 57 80 { 58 81 config: createConfig({
+4 -16
packages/openapi-ts/test/__snapshots__/3.1.0/duplicate-null/types.gen.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - export type PostTestData = { 4 - /** 5 - * should not produce duplicate null 6 - */ 7 - body?: { 8 - weirdEnum?: ('' | (string) | null); 9 - }; 10 - }; 11 - 12 - export type $OpenApiTs = { 13 - '/test': { 14 - post: { 15 - req: PostTestData; 16 - }; 17 - }; 18 - }; 3 + /** 4 + * should not produce duplicate null 5 + */ 6 + export type WeirdEnum = '' | string | null;
+2
packages/openapi-ts/test/__snapshots__/3.1.0/object-properties-all-of/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen';
+11
packages/openapi-ts/test/__snapshots__/3.1.0/object-properties-all-of/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type Foo = { 4 + bar: string; 5 + } & { 6 + baz: string; 7 + } & { 8 + foo: string; 9 + bar?: string; 10 + baz?: string; 11 + };
+3 -5
packages/openapi-ts/test/__snapshots__/3.1.0/object-properties-any-of/types.gen.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - export type Foo = { 3 + export type Foo = ({ 4 4 bar: string; 5 5 } | { 6 6 baz: string; 7 - } | { 7 + }) & { 8 8 foo: string; 9 9 bar?: string; 10 10 baz?: string; 11 - }; 12 - 13 - export type $OpenApiTs = unknown; 11 + };
+2
packages/openapi-ts/test/__snapshots__/3.1.0/object-properties-one-of/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen';
+11
packages/openapi-ts/test/__snapshots__/3.1.0/object-properties-one-of/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type Foo = ({ 4 + bar: string; 5 + } | { 6 + baz: string; 7 + }) & { 8 + foo: string; 9 + bar?: string; 10 + baz?: string; 11 + };
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.0/required-all-of-ref/types.gen.ts
··· 6 6 }; 7 7 8 8 export type Bar = Foo & { 9 + bar: number; 9 10 foo: string; 10 11 baz: string; 11 - bar: number; 12 12 };
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.0/required-any-of-ref/types.gen.ts
··· 5 5 baz?: string; 6 6 }; 7 7 8 - export type Bar = Foo | { 8 + export type Bar = Foo & { 9 9 bar: number; 10 10 };
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.0/required-one-of-ref/types.gen.ts
··· 5 5 baz?: string; 6 6 }; 7 7 8 - export type Bar = (Foo) & { 8 + export type Bar = Foo & { 9 9 bar: number; 10 10 };
+3 -3
packages/openapi-ts/test/sample.cjs
··· 17 17 // input: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 18 18 // name: 'foo', 19 19 output: { 20 - format: 'prettier', 21 - lint: 'eslint', 20 + // format: 'prettier', 21 + // lint: 'eslint', 22 22 path: './test/generated/sample/', 23 23 }, 24 24 // plugins: [ ··· 30 30 export: false, 31 31 }, 32 32 services: { 33 + // asClass: true, 33 34 // export: false, 34 - asClass: true, 35 35 // filter: '^GET /api/v{api-version}/simple:operation$', 36 36 // export: false, 37 37 // name: '^Parameters',
+15 -27
packages/openapi-ts/test/spec/3.1.0/duplicate-null.json
··· 3 3 "info": { 4 4 "version": "1.0.0" 5 5 }, 6 - "paths": { 7 - "/test": { 8 - "post": { 9 - "requestBody": { 10 - "description": "should not produce duplicate null", 11 - "content": { 12 - "application/json": { 13 - "schema": { 14 - "type": "object", 15 - "properties": { 16 - "weirdEnum": { 17 - "oneOf": [ 18 - { 19 - "type": "string", 20 - "enum": [""] 21 - }, 22 - { 23 - "type": "string", 24 - "nullable": true 25 - } 26 - ], 27 - "nullable": true 28 - } 29 - } 30 - } 31 - } 6 + "components": { 7 + "schemas": { 8 + "WeirdEnum": { 9 + "description": "should not produce duplicate null", 10 + "oneOf": [ 11 + { 12 + "type": "string", 13 + "enum": [""] 14 + }, 15 + { 16 + "type": ["string", "null"] 17 + }, 18 + { 19 + "type": "null" 32 20 } 33 - } 21 + ] 34 22 } 35 23 } 36 24 }
+28
packages/openapi-ts/test/spec/3.1.0/object-properties-all-of.json
··· 1 + { 2 + "openapi": "3.1.0", 3 + "info": { 4 + "version": "1.0.0" 5 + }, 6 + "components": { 7 + "schemas": { 8 + "Foo": { 9 + "properties": { 10 + "foo": { "type": "string" }, 11 + "bar": { "type": "string" }, 12 + "baz": { "type": "string" } 13 + }, 14 + "required": ["foo"], 15 + "allOf": [ 16 + { 17 + "properties": { "bar": { "type": "string" } }, 18 + "required": ["bar"] 19 + }, 20 + { 21 + "properties": { "baz": { "type": "string" } }, 22 + "required": ["baz"] 23 + } 24 + ] 25 + } 26 + } 27 + } 28 + }
+28
packages/openapi-ts/test/spec/3.1.0/object-properties-one-of.json
··· 1 + { 2 + "openapi": "3.1.0", 3 + "info": { 4 + "version": "1.0.0" 5 + }, 6 + "components": { 7 + "schemas": { 8 + "Foo": { 9 + "properties": { 10 + "foo": { "type": "string" }, 11 + "bar": { "type": "string" }, 12 + "baz": { "type": "string" } 13 + }, 14 + "required": ["foo"], 15 + "oneOf": [ 16 + { 17 + "properties": { "bar": { "type": "string" } }, 18 + "required": ["bar"] 19 + }, 20 + { 21 + "properties": { "baz": { "type": "string" } }, 22 + "required": ["baz"] 23 + } 24 + ] 25 + } 26 + } 27 + } 28 + }