@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
JavaScript
;
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