openapi-alchemist
Version:
Transform OpenAPI 3 to Swagger 2 with alchemical precision
305 lines (304 loc) • 10.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Util = exports.BaseFormat = void 0;
const Yaml = __importStar(require("js-yaml"));
const fs = __importStar(require("fs"));
function deepSortObject(obj, compareFn) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepSortObject(item, compareFn));
}
const sortedKeys = Object.keys(obj).sort(compareFn);
const sortedObj = {};
for (const key of sortedKeys) {
sortedObj[key] = deepSortObject(obj[key], compareFn);
}
return sortedObj;
}
class BaseFormat {
spec;
format;
converters;
sourceType;
source;
subResources;
constructor(spec) {
if (spec)
this.spec = spec;
this.format = 'base_format';
this.converters = {};
}
stringify(options) {
const syntax = options?.syntax || 'json';
const order = options?.order || 'openapi';
let sortedSpecs;
if (order === 'false') {
sortedSpecs = this.spec;
}
else {
const attr = [
'externalDocs',
'tags',
'security',
'securityDefinitions',
'responses',
'parameters',
'definitions',
'components',
'paths',
'produces',
'consumes',
'schemes',
'basePath',
'host',
'servers',
'info',
'swagger',
'openapi',
];
sortedSpecs = deepSortObject(this.spec, (a, b) => {
let aIdx = -1;
let bIdx = -1;
if (order !== 'alpha') {
aIdx = attr.indexOf(a);
bIdx = attr.indexOf(b);
}
if (aIdx === -1 && bIdx === -1) {
if (a === b)
return 0;
return a < b ? -1 : 1;
}
if (aIdx === bIdx)
return 0;
return aIdx < bIdx ? 1 : -1;
});
}
if (syntax === 'yaml') {
return Yaml.dump(sortedSpecs);
}
else {
return JSON.stringify(sortedSpecs, null, 2);
}
}
fillMissing(_dummyData) {
}
validate(callback) {
const result = Promise.resolve({ errors: null, warnings: null });
if (callback) {
result.then(result => callback(null, result)).catch(err => callback(err, null));
}
return result;
}
listSubResources() {
return {};
}
resolveSubResources() {
const sources = this.listSubResources();
return Promise.all(Object.values(sources).map((url) => this.readSpec(url))).then((resources) => {
const refs = Object.keys(sources);
this.subResources = Object.fromEntries(refs.map((key, index) => [key, resources[index]]));
});
}
parse(data) {
if (typeof data !== 'string') {
return Promise.resolve(data);
}
const parsePromises = Object.values(this.parsers).map((parser) => parser(data).catch((err) => {
return Promise.reject(err);
}));
return Promise.allSettled(parsePromises).then(results => {
const successful = results.find(result => result.status === 'fulfilled');
if (successful) {
return successful.value;
}
const errors = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
throw new Error('Failed to parse spec: ' + errors.map((e) => e.message).join(', '));
});
}
readSpec(source) {
const sourceType = Util.getSourceType(source);
if (!sourceType) {
throw new Error('Spec source should be object, string, or filename.');
}
return Util.resourceReaders[sourceType](source)
.then((data) => this.parse(data))
.then((spec) => [spec, sourceType]);
}
resolveResources(source) {
return this.readSpec(source)
.then(([spec, sourceType]) => {
if (!this.checkFormat(spec)) {
throw new Error(sourceType + ' ' + source + ' is not valid ' + this.format);
}
this.spec = spec;
this.sourceType = sourceType;
if (sourceType === 'file') {
this.source = source;
}
})
.then(() => this.resolveSubResources())
.then(() => {
this.fixSpec();
const version = this.getFormatVersion();
if (this.supportedVersions.indexOf(version) === -1) {
throw new Error('Unsupported version');
}
});
}
convertTo(format, passthrough, callback) {
if (format === this.format) {
const result = Promise.resolve(this);
if (callback) {
result.then(result => callback(null, result)).catch(err => callback(err, null));
}
return result;
}
const convert = this.converters[format];
if (!convert) {
const error = new Error(`Unable to convert from ${this.format} to ${format}`);
if (callback) {
callback(error, null);
return Promise.resolve(this);
}
return Promise.reject(error);
}
const result = convert(this, passthrough).then((spec) => {
const FormatClass = global.Formats?.[format];
if (!FormatClass) {
throw new Error(`Format ${format} not found in registry`);
}
const result = new FormatClass(spec);
result.fixSpec();
return result;
}, (err) => {
err.message = 'Error during conversion: ' + err.message;
throw err;
});
if (callback) {
result.then(result => callback(null, result)).catch(err => callback(err, null));
}
return result;
}
convertTransitive(intermediaries, passthrough) {
let prom = Promise.resolve(this);
intermediaries.forEach(intermediary => {
prom = prom.then((spec) => {
return spec.convertTo(intermediary, passthrough);
});
});
return prom.then((spec) => spec.spec);
}
}
exports.BaseFormat = BaseFormat;
class Util {
static joinPath(...args) {
return args.join('/').replace(/\/\/+/g, '/');
}
static parseJSON = (data) => {
try {
return Promise.resolve(JSON.parse(data));
}
catch (err) {
return Promise.reject(err);
}
};
static parseYAML = (data) => Promise.resolve(Yaml.load(data));
static resourceReaders = {
file: (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err)
reject(err);
else
resolve(data);
});
});
},
object: (data) => Promise.resolve(data),
string: (data) => Promise.resolve(data),
};
static getSourceType(source) {
if (typeof source === 'object' && source !== null) {
return 'object';
}
if (typeof source !== 'string') {
return undefined;
}
try {
if (fs.existsSync(source))
return 'file';
}
catch {
}
if (source.startsWith('./') || source.startsWith('../') || !source.includes('://')) {
return 'file';
}
else {
return 'string';
}
}
static removeNonValues(obj) {
if (obj === null || typeof obj !== 'object') {
return;
}
if (Array.isArray(obj)) {
for (let i = obj.length - 1; i >= 0; i--) {
if (obj[i] === undefined) {
obj.splice(i, 1);
}
else {
this.removeNonValues(obj[i]);
}
}
}
else {
const keys = Object.keys(obj);
for (const key of keys) {
if (obj[key] === undefined) {
delete obj[key];
}
else {
this.removeNonValues(obj[key]);
}
}
}
}
}
exports.Util = Util;