UNPKG

@spec2ts/openapi-client

Version:

Utility to convert OpenAPI v3 specifications to Typescript HTTP client using TypeScript native compiler

156 lines (155 loc) 7.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateFunctions = exports.generateDefaults = exports.generateServers = void 0; const ts = require("typescript"); const core = require("@spec2ts/core"); const core_parser_1 = require("@spec2ts/jsonschema/lib/core-parser"); const server_parser_1 = require("./server-parser"); const core_parser_2 = require("./core-parser"); const util_1 = require("./util"); //#region Public function generateServers(file, { servers }, context) { servers = servers || []; if (context.options.baseUrl) servers = [{ url: context.options.baseUrl }]; const serversConst = core.findFirstVariableStatement(file.statements, "servers"); const defaultsConst = core.findFirstVariableStatement(file.statements, "defaults"); if (!serversConst || !defaultsConst) { throw new Error("Invalid template: missing servers or defaults const"); } file = core.replaceSourceFileStatement(file, serversConst, core.updateVariableStatementValue(serversConst, "servers", (0, server_parser_1.parseServers)(servers))); file = core.replaceSourceFileStatement(file, defaultsConst, core.updateVariableStatementPropertyValue(defaultsConst, "defaults", "baseUrl", (0, server_parser_1.defaultBaseUrl)(servers))); return file; } exports.generateServers = generateServers; function generateDefaults(file, context) { if (context.options.importFetch) { const defaultsConst = core.findFirstVariableStatement(file.statements, "defaults"); if (!defaultsConst) { throw new Error("Invalid template: missing defaults const"); } file = core.prependSourceFileStatements(file, core.createDefaultImportDeclaration({ moduleSpecifier: context.options.importFetch, name: "fetch", bindings: ["RequestInit", "Headers"] }), core.createNamespaceImportDeclaration({ moduleSpecifier: "form-data", name: "FormData" })); file = core.replaceSourceFileStatement(file, defaultsConst, core.updateVariableStatementPropertyValue(defaultsConst, "defaults", "fetch", core.toExpression("fetch"))); } return file; } exports.generateDefaults = generateDefaults; function generateFunctions(file, spec, context) { var _a; const paths = Object.fromEntries(Object.entries((_a = spec.paths) !== null && _a !== void 0 ? _a : {}) .filter(([path]) => !context.options.prefix || path.startsWith(context.options.prefix))); const functions = Object.entries(paths).map(([path, pathSpec]) => { const item = (0, core_parser_1.resolveReference)(pathSpec, context); return Object.entries(item) .filter(([verb,]) => (0, core_parser_2.isMethod)(verb.toUpperCase())) .map(([verb, entry]) => generateFunction(path, item, verb.toUpperCase(), entry, context)); }).flat(); if (context.options.typesPath && context.typesFile) { context.typesFile = core.updateSourceFileStatements(context.typesFile, context.aliases); file = core.updateSourceFileStatements(file, [ core.createNamedImportDeclaration({ moduleSpecifier: context.options.typesPath, bindings: context.aliases.map(a => a.name.text) }), ...file.statements, ...functions ]); } else { file = core.appendSourceFileStatements(file, ...context.aliases, ...functions); } return file; } exports.generateFunctions = generateFunctions; function generateFunction(path, item, method, operation, context) { const { name, query, header, paramsVars, args, bodyMode, bodyVar, response, responseVoid, responseJSON } = (0, core_parser_2.parseOperation)(path, item, method, operation, context); const qs = generateQs(query, paramsVars); const url = generateUrl(path, qs); const init = [ ts.factory.createSpreadAssignment(ts.factory.createIdentifier("options")), ]; if (method !== "GET") { init.push(ts.factory.createPropertyAssignment("method", ts.factory.createStringLiteral(method))); } if (bodyVar) { init.push(core.createPropertyAssignment("body", ts.factory.createIdentifier(bodyVar))); } if (header.length) { init.push(ts.factory.createPropertyAssignment("headers", ts.factory.createObjectLiteralExpression([ ts.factory.createSpreadAssignment(ts.factory.createPropertyAccessChain(ts.factory.createIdentifier("options"), core.questionDotToken, ts.factory.createIdentifier("headers"))), ...header.map(({ name }) => core.createPropertyAssignment(name, ts.factory.createIdentifier(paramsVars[name]))), ], true))); } const fetchArgs = [url]; if (init.length) { const initObj = ts.factory.createObjectLiteralExpression(init, true); fetchArgs.push(bodyMode ? callFunction("http", bodyMode, [initObj]) : initObj); } return core.addComment(core.createFunctionDeclaration(name, { modifiers: [core.modifier.export, core.modifier.async], type: ts.factory.createTypeReferenceNode("Promise", [ ts.factory.createTypeReferenceNode("ApiResponse", [response]) ]) }, args, core.block(ts.factory.createReturnStatement(ts.factory.createAwaitExpression(callFunction("http", responseJSON ? "fetchJson" : responseVoid ? "fetchVoid" : "fetch", fetchArgs))))), operation.summary || operation.description); } function generateQs(parameters, paramsVars) { if (!parameters.length) { return; } const paramsByFormatter = groupByFormatter(parameters); return callFunction("QS", "query", Object.entries(paramsByFormatter).map(([format, params]) => { return callFunction("QS", format, [ core.createObjectLiteral(params.map((p) => [p.name, paramsVars[p.name]])), ]); })); } function generateUrl(path, qs) { const spans = []; // Use a replacer function to collect spans as a side effect: const head = path.replace(/(.*?)\{(.+?)\}(.*?)(?=\{|$)/g, (_, head, name, literal) => { const expression = (0, util_1.camelCase)(name); spans.push({ expression: ts.factory.createIdentifier(expression), literal }); return head; }); if (qs) { // add the query string as last span spans.push({ expression: qs, literal: "" }); } return core.createTemplateString(head, spans); } //#endregion //#region Utils function callFunction(ns, name, args) { return core.createCall(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(ns), name), { args }); } function groupByFormatter(parameters) { const res = {}; parameters.forEach(param => { const formatter = getFormatter(param); res[formatter] = res[formatter] || []; res[formatter].push(param); }); return res; } function getFormatter({ style, explode }) { if (style === "spaceDelimited") return "space"; if (style === "pipeDelimited") return "pipe"; if (style === "deepObject") return "deep"; if (style === "form") { return explode === false ? "form" : "explode"; } return explode ? "explode" : "form"; } //#endregion