UNPKG

openapi-typescript

Version:

Generate TypeScript types from Swagger OpenAPI specs

181 lines 7.02 kB
import fetch from "node-fetch"; import fs from "fs"; import path from "path"; import { URL } from "url"; import slash from "slash"; import mime from "mime"; import yaml from "js-yaml"; import { red } from "kleur"; import { parseRef } from "./utils"; export const VIRTUAL_JSON_URL = `file:///_json`; function parseSchema(schema, type) { if (type === "YAML") { try { return yaml.load(schema); } catch (err) { throw new Error(`YAML: ${err.toString()}`); } } else { try { return JSON.parse(schema); } catch (err) { throw new Error(`JSON: ${err.toString()}`); } } } function isFile(url) { return url.protocol === "file:"; } export function resolveSchema(url) { if (url.startsWith("http://") || url.startsWith("https://")) { return new URL(url); } const localPath = path.isAbsolute(url) ? new URL("", `file://${slash(url)}`) : new URL(url, `file://${slash(process.cwd())}/`); if (!fs.existsSync(localPath)) { throw new Error(`Could not locate ${url}`); } else if (fs.statSync(localPath).isDirectory()) { throw new Error(`${localPath} is a directory not a file`); } return localPath; } function parseHttpHeaders(httpHeaders) { const finalHeaders = {}; for (const [k, v] of Object.entries(httpHeaders)) { if (typeof v === "string") { finalHeaders[k] = v; } else { try { const stringVal = JSON.stringify(v); finalHeaders[k] = stringVal; } catch (err) { console.error(red(`Cannot parse key: ${k} into JSON format. Continuing with the next HTTP header that is specified`)); } } } return finalHeaders; } export default async function load(schema, options) { const urlCache = options.urlCache || new Set(); const isJSON = schema instanceof URL === false; let schemaID = isJSON ? new URL(VIRTUAL_JSON_URL).href : schema.href; const schemas = options.schemas; if (isJSON) { schemas[schemaID] = schema; } else { if (urlCache.has(schemaID)) return options.schemas; urlCache.add(schemaID); let contents = ""; let contentType = ""; const schemaURL = schema; if (isFile(schemaURL)) { contents = await fs.promises.readFile(schemaURL, "utf8"); contentType = mime.getType(schemaID) || ""; } else { const headers = { "User-Agent": "openapi-typescript", }; if (options.auth) headers.Authorizaton = options.auth; if (options.httpHeaders) { const parsedHeaders = parseHttpHeaders(options.httpHeaders); for (const [k, v] of Object.entries(parsedHeaders)) { headers[k] = v; } } const res = await fetch(schemaID, { method: options.httpMethod || "GET", headers }); contentType = res.headers.get("Content-Type") || ""; contents = await res.text(); } const isYAML = contentType === "application/openapi+yaml" || contentType === "text/yaml"; const isJSON = contentType === "application/json" || contentType === "application/json5" || contentType === "application/openapi+json"; if (isYAML) { schemas[schemaID] = parseSchema(contents, "YAML"); } else if (isJSON) { schemas[schemaID] = parseSchema(contents, "JSON"); } else { try { schemas[schemaID] = parseSchema(contents, "JSON"); } catch (err1) { try { schemas[schemaID] = parseSchema(contents, "YAML"); } catch (err2) { throw new Error(`Unknown format${contentType ? `: "${contentType}"` : ""}. Only YAML or JSON supported.`); } } } } const refPromises = []; schemas[schemaID] = JSON.parse(JSON.stringify(schemas[schemaID]), (k, v) => { if (k !== "$ref" || typeof v !== "string") return v; const { url: refURL } = parseRef(v); if (refURL) { const isRemoteURL = refURL.startsWith("http://") || refURL.startsWith("https://"); if (isJSON && !isRemoteURL) { throw new Error(`Can’t load URL "${refURL}" from dynamic JSON. Load this schema from a URL instead.`); } const nextURL = isRemoteURL ? new URL(refURL) : new URL(slash(refURL), schema); refPromises.push(load(nextURL, { ...options, urlCache }).then((subschemas) => { for (const subschemaURL of Object.keys(subschemas)) { schemas[subschemaURL] = subschemas[subschemaURL]; } })); return v.replace(refURL, nextURL.href); } return v; }); await Promise.all(refPromises); if (schemaID === options.rootURL.href) { for (const subschemaURL of Object.keys(schemas)) { schemas[subschemaURL] = JSON.parse(JSON.stringify(schemas[subschemaURL]), (k, v) => { if (k !== "$ref" || typeof v !== "string") return v; if (!v.includes("#")) return v; const { url, parts } = parseRef(v); if (url && new URL(url).href !== options.rootURL.href) { const relativeURL = isFile(new URL(url)) && isFile(options.rootURL) ? path.posix.relative(path.posix.dirname(options.rootURL.href), url) : url; return `external["${relativeURL}"]["${parts.join('"]["')}"]`; } if (!url && subschemaURL !== options.rootURL.href) { const relativeURL = isFile(new URL(subschemaURL)) && isFile(options.rootURL) ? path.posix.relative(path.posix.dirname(options.rootURL.href), subschemaURL) : subschemaURL; return `external["${relativeURL}"]["${parts.join('"]["')}"]`; } const [base, ...rest] = parts; return `${base}["${rest.join('"]["')}"]`; }); if (subschemaURL !== options.rootURL.href) { const relativeURL = isFile(new URL(subschemaURL)) && isFile(options.rootURL) ? path.posix.relative(path.posix.dirname(options.rootURL.href), subschemaURL) : subschemaURL; if (relativeURL !== subschemaURL) { schemas[relativeURL] = schemas[subschemaURL]; delete schemas[subschemaURL]; } } } } return schemas; } //# sourceMappingURL=load.js.map