fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 92 lines 2.9 kB view raw
1import type { IR } from './types'; 2 3/** 4 * Ensure we don't produce redundant types, e.g. string | string. 5 */ 6export function deduplicateSchema<T extends IR.SchemaObject>({ 7 detectFormat = true, 8 schema, 9}: { 10 detectFormat?: boolean; 11 schema: T; 12}): T { 13 if (!schema.items) { 14 return schema; 15 } 16 17 const uniqueItems: Array<IR.SchemaObject> = []; 18 const typeIds: Array<string> = []; 19 20 for (const item of schema.items) { 21 // skip nested schemas for now, handle if necessary 22 if ((!item.type && item.items) || schema.type === 'tuple') { 23 uniqueItems.push(item); 24 continue; 25 } 26 27 if ( 28 // no `type` might still include `$ref` or `const` 29 !item.type || 30 item.type === 'boolean' || 31 item.type === 'integer' || 32 item.type === 'null' || 33 item.type === 'number' || 34 item.type === 'string' || 35 item.type === 'unknown' || 36 item.type === 'void' 37 ) { 38 // const needs namespace to handle empty string values, otherwise 39 // fallback would equal an actual value and we would skip an item 40 const constant = item.const !== undefined ? `const-${item.const}` : ''; 41 const format = item.format !== undefined && detectFormat ? `format-${item.format}` : ''; 42 43 // Include validation constraints in the type ID to avoid incorrect deduplication 44 const constraints = [ 45 item.minLength !== undefined ? `minLength-${item.minLength}` : '', 46 item.maxLength !== undefined ? `maxLength-${item.maxLength}` : '', 47 item.minimum !== undefined ? `minimum-${item.minimum}` : '', 48 item.maximum !== undefined ? `maximum-${item.maximum}` : '', 49 item.exclusiveMinimum !== undefined ? `exclusiveMinimum-${item.exclusiveMinimum}` : '', 50 item.exclusiveMaximum !== undefined ? `exclusiveMaximum-${item.exclusiveMaximum}` : '', 51 item.minItems !== undefined ? `minItems-${item.minItems}` : '', 52 item.maxItems !== undefined ? `maxItems-${item.maxItems}` : '', 53 item.pattern !== undefined ? `pattern-${item.pattern}` : '', 54 ].join(''); 55 56 const typeId = `${item.$ref ?? ''}${item.type ?? ''}${constant}${format}${constraints}`; 57 if (!typeIds.includes(typeId)) { 58 typeIds.push(typeId); 59 uniqueItems.push(item); 60 } 61 continue; 62 } 63 64 uniqueItems.push(item); 65 } 66 67 let result = { ...schema }; 68 result.items = uniqueItems; 69 70 if ( 71 result.items.length <= 1 && 72 result.type !== 'array' && 73 result.type !== 'enum' && 74 result.type !== 'tuple' 75 ) { 76 // bring the only item up to clean up the schema 77 const liftedSchema = result.items[0]; 78 delete result.logicalOperator; 79 delete result.items; 80 result = { 81 ...result, 82 ...liftedSchema, 83 }; 84 } 85 86 // exclude unknown if it's the only type left 87 if (result.type === 'unknown') { 88 return {} as T; 89 } 90 91 return result; 92}