fork of hey-api/openapi-ts because I need some additional things
at feat/use-query-options-param 139 lines 4.4 kB view raw
1const jsonPointerSlash = /~1/g; 2const jsonPointerTilde = /~0/g; 3 4/** 5 * Returns the reusable component name from `$ref`. 6 */ 7export function refToName($ref: string): string { 8 const path = jsonPointerToPath($ref); 9 const name = path[path.length - 1]!; 10 // refs using unicode characters become encoded, didn't investigate why 11 // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 12 return decodeURI(name); 13} 14 15/** 16 * Encodes a path segment for use in a JSON Pointer (RFC 6901). 17 * 18 * - Replaces all '~' with '~0'. 19 * - Replaces all '/' with '~1'. 20 * 21 * This ensures that path segments containing these characters are safely 22 * represented in JSON Pointer strings. 23 * 24 * @param segment - The path segment (string or number) to encode. 25 * @returns The encoded segment as a string. 26 */ 27export function encodeJsonPointerSegment(segment: string | number): string { 28 return String(segment).replace(/~/g, '~0').replace(/\//g, '~1'); 29} 30 31/** 32 * Converts a JSON Pointer string (RFC 6901) to an array of path segments. 33 * 34 * - Removes the leading '#' if present. 35 * - Splits the pointer on '/'. 36 * - Decodes '~1' to '/' and '~0' to '~' in each segment. 37 * - Returns an empty array for the root pointer ('#' or ''). 38 * 39 * @param pointer - The JSON Pointer string to convert (e.g., '#/components/schemas/Foo'). 40 * @returns An array of decoded path segments. 41 */ 42export function jsonPointerToPath(pointer: string): ReadonlyArray<string> { 43 let clean = pointer.trim(); 44 if (clean.startsWith('#')) { 45 clean = clean.slice(1); 46 } 47 if (clean.startsWith('/')) { 48 clean = clean.slice(1); 49 } 50 if (!clean) { 51 return []; 52 } 53 return clean 54 .split('/') 55 .map((part) => part.replace(jsonPointerSlash, '/').replace(jsonPointerTilde, '~')); 56} 57 58/** 59 * Normalizes a JSON Pointer string to a canonical form. 60 * 61 * - Ensures the pointer starts with '#'. 62 * - Removes trailing slashes (except for root). 63 * - Collapses multiple consecutive slashes into one. 64 * - Trims whitespace from the input. 65 * 66 * @param pointer - The JSON Pointer string to normalize. 67 * @returns The normalized JSON Pointer string. 68 */ 69export function normalizeJsonPointer(pointer: string): string { 70 let normalized = pointer.trim(); 71 if (!normalized.startsWith('#')) { 72 normalized = `#${normalized}`; 73 } 74 // Remove trailing slashes (except for root) 75 if (normalized.length > 1 && normalized.endsWith('/')) { 76 normalized = normalized.slice(0, -1); 77 } 78 // Collapse multiple slashes 79 normalized = normalized.replace(/\/+/g, '/'); 80 return normalized; 81} 82 83/** 84 * Encode path as JSON Pointer (RFC 6901). 85 * 86 * @param path 87 * @returns 88 */ 89export function pathToJsonPointer(path: ReadonlyArray<string | number>): string { 90 const segments = path.map(encodeJsonPointerSegment).join('/'); 91 return '#' + (segments ? `/${segments}` : ''); 92} 93 94/** 95 * Checks if a $ref or path points to a top-level component (not a deep path reference). 96 * 97 * Top-level component references: 98 * - OpenAPI 3.x: #/components/{type}/{name} (3 segments) 99 * - OpenAPI 2.0: #/definitions/{name} (2 segments) 100 * 101 * Deep path references (4+ segments for 3.x, 3+ for 2.0) should be inlined 102 * because they don't have corresponding registered symbols. 103 * 104 * @param refOrPath - The $ref string or path array to check 105 * @returns true if the ref points to a top-level component, false otherwise 106 */ 107export function isTopLevelComponent(refOrPath: string | ReadonlyArray<string | number>): boolean { 108 const path = refOrPath instanceof Array ? refOrPath : jsonPointerToPath(refOrPath); 109 110 // OpenAPI 3.x: #/components/{type}/{name} = 3 segments 111 if (path[0] === 'components') { 112 return path.length === 3; 113 } 114 115 // OpenAPI 2.0: #/definitions/{name} = 2 segments 116 if (path[0] === 'definitions') { 117 return path.length === 2; 118 } 119 120 return false; 121} 122 123export function resolveRef<T>({ $ref, spec }: { $ref: string; spec: Record<string, any> }): T { 124 // refs using unicode characters become encoded, didn't investigate why 125 // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 126 const path = jsonPointerToPath(decodeURI($ref)); 127 128 let current = spec; 129 130 for (const part of path) { 131 const segment = part as keyof typeof current; 132 if (current[segment] === undefined) { 133 throw new Error(`Reference not found: ${$ref}`); 134 } 135 current = current[segment]; 136 } 137 138 return current as T; 139}