fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 236 lines 7.5 kB view raw
1import { ono } from '@jsdevtools/ono'; 2import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from 'json-schema'; 3 4import type { ParserOptions } from './options'; 5import $Ref from './ref'; 6import type { JSONSchema } from './types'; 7import convertPathToPosix from './util/convert-path-to-posix'; 8import * as url from './util/url'; 9 10interface $RefsMap<S extends object = JSONSchema> { 11 [url: string]: $Ref<S>; 12} 13/** 14 * When you call the resolve method, the value that gets passed to the callback function (or Promise) is a $Refs object. This same object is accessible via the parser.$refs property of $RefParser objects. 15 * 16 * This object is a map of JSON References and their resolved values. It also has several convenient helper methods that make it easy for you to navigate and manipulate the JSON References. 17 * 18 * See https://apitools.dev/json-schema-ref-parser/docs/refs.html 19 */ 20export default class $Refs<S extends object = JSONSchema> { 21 /** 22 * This property is true if the schema contains any circular references. You may want to check this property before serializing the dereferenced schema as JSON, since JSON.stringify() does not support circular references by default. 23 * 24 * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#circular 25 */ 26 public circular: boolean; 27 28 /** 29 * Returns the paths/URLs of all the files in your schema (including the main schema file). 30 * 31 * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#pathstypes 32 * 33 * @param types (optional) Optionally only return certain types of paths ("file", "http", etc.) 34 */ 35 paths(...types: (string | string[])[]): string[] { 36 const paths = getPaths(this._$refs, types.flat()); 37 return paths.map((path) => convertPathToPosix(path.decoded)); 38 } 39 40 /** 41 * Returns a map of paths/URLs and their correspond values. 42 * 43 * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#valuestypes 44 * 45 * @param types (optional) Optionally only return values from certain locations ("file", "http", etc.) 46 */ 47 values(...types: (string | string[])[]): S { 48 const $refs = this._$refs; 49 const paths = getPaths($refs, types.flat()); 50 return paths.reduce<Record<string, any>>((obj, path) => { 51 obj[convertPathToPosix(path.decoded)] = $refs[path.encoded]!.value; 52 return obj; 53 }, {}) as S; 54 } 55 56 /** 57 * Returns `true` if the given path exists in the schema; otherwise, returns `false` 58 * 59 * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#existsref 60 * 61 * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash 62 */ 63 /** 64 * Determines whether the given JSON reference exists. 65 * 66 * @param path - The path being resolved, optionally with a JSON pointer in the hash 67 * @param [options] 68 * @returns 69 */ 70 exists(path: string, options: any) { 71 try { 72 this._resolve(path, '', options); 73 return true; 74 } catch { 75 return false; 76 } 77 } 78 79 /** 80 * Resolves the given JSON reference and returns the resolved value. 81 * 82 * @param path - The path being resolved, with a JSON pointer in the hash 83 * @param [options] 84 * @returns - Returns the resolved value 85 */ 86 get(path: string, options?: ParserOptions): JSONSchema4Type | JSONSchema6Type | JSONSchema7Type { 87 return this._resolve(path, '', options)!.value; 88 } 89 90 /** 91 * Sets the value at the given path in the schema. If the property, or any of its parents, don't exist, they will be created. 92 * 93 * @param path The JSON Reference path, optionally with a JSON Pointer in the hash 94 * @param value The value to assign. Can be anything (object, string, number, etc.) 95 */ 96 set(path: string, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) { 97 const absPath = url.resolve(this._root$Ref.path!, path); 98 const withoutHash = url.stripHash(absPath); 99 const $ref = this._$refs[withoutHash]; 100 101 if (!$ref) { 102 throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`); 103 } 104 105 $ref.set(absPath, value); 106 } 107 /** 108 * Returns the specified {@link $Ref} object, or undefined. 109 * 110 * @param path - The path being resolved, optionally with a JSON pointer in the hash 111 * @returns 112 * @protected 113 */ 114 _get$Ref(path: string) { 115 path = url.resolve(this._root$Ref.path!, path); 116 const withoutHash = url.stripHash(path); 117 return this._$refs[withoutHash]; 118 } 119 120 /** 121 * Creates a new {@link $Ref} object and adds it to this {@link $Refs} object. 122 * 123 * @param path - The file path or URL of the referenced file 124 */ 125 _add(path: string) { 126 const withoutHash = url.stripHash(path); 127 128 const $ref = new $Ref<S>(this); 129 $ref.path = withoutHash; 130 131 this._$refs[withoutHash] = $ref; 132 this._root$Ref = this._root$Ref || $ref; 133 134 return $ref; 135 } 136 137 /** 138 * Resolves the given JSON reference. 139 * 140 * @param path - The path being resolved, optionally with a JSON pointer in the hash 141 * @param pathFromRoot - The path of `obj` from the schema root 142 * @param [options] 143 * @returns 144 * @protected 145 */ 146 _resolve(path: string, pathFromRoot: string, options?: ParserOptions) { 147 const absPath = url.resolve(this._root$Ref.path!, path); 148 const withoutHash = url.stripHash(absPath); 149 const $ref = this._$refs[withoutHash]; 150 151 if (!$ref) { 152 throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`); 153 } 154 155 if ($ref.value === undefined) { 156 console.warn(`$ref entry exists but value is undefined: ${withoutHash}`); 157 return null; // Treat as unresolved 158 } 159 160 return $ref.resolve(absPath, options, path, pathFromRoot); 161 } 162 163 /** 164 * A map of paths/urls to {@link $Ref} objects 165 * 166 * @type {object} 167 * @protected 168 */ 169 _$refs: $RefsMap<S> = {}; 170 171 /** 172 * The {@link $Ref} object that is the root of the JSON schema. 173 * 174 * @type {$Ref} 175 * @protected 176 */ 177 _root$Ref: $Ref<S>; 178 179 constructor() { 180 /** 181 * Indicates whether the schema contains any circular references. 182 * 183 * @type {boolean} 184 */ 185 this.circular = false; 186 187 this._$refs = {}; 188 189 // @ts-ignore 190 this._root$Ref = null; 191 } 192 193 /** 194 * Returns the paths of all the files/URLs that are referenced by the JSON schema, 195 * including the schema itself. 196 * 197 * @param [types] - Only return paths of the given types ("file", "http", etc.) 198 * @returns 199 */ 200 /** 201 * Returns the map of JSON references and their resolved values. 202 * 203 * @param [types] - Only return references of the given types ("file", "http", etc.) 204 * @returns 205 */ 206 207 /** 208 * Returns a POJO (plain old JavaScript object) for serialization as JSON. 209 * 210 * @returns {object} 211 */ 212 toJSON = this.values; 213} 214 215/** 216 * Returns the encoded and decoded paths keys of the given object. 217 * 218 * @param $refs - The object whose keys are URL-encoded paths 219 * @param [types] - Only return paths of the given types ("file", "http", etc.) 220 * @returns 221 */ 222function getPaths<S extends object = JSONSchema>($refs: $RefsMap<S>, types: string[]) { 223 let paths = Object.keys($refs); 224 225 // Filter the paths by type 226 types = Array.isArray(types[0]) ? types[0] : Array.prototype.slice.call(types); 227 if (types.length > 0 && types[0]) { 228 paths = paths.filter((key) => types.includes($refs[key]!.pathType as string)); 229 } 230 231 // Decode local filesystem paths 232 return paths.map((path) => ({ 233 decoded: $refs[path]!.pathType === 'file' ? url.toFileSystemPath(path, true) : path, 234 encoded: path, 235 })); 236}