fork of hey-api/openapi-ts because I need some additional things
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}