redoc/lib/utils/SpecManager.ts

176 lines
4.9 KiB
TypeScript
Raw Normal View History

'use strict';
2016-06-15 23:33:57 +03:00
import JsonSchemaRefParser from 'json-schema-ref-parser';
2015-10-09 10:50:02 +03:00
import JsonPointer from './JsonPointer';
import { renderMd, safePush } from './helpers';
2016-07-26 12:03:15 +03:00
import slugify from 'slugify';
import { parse as urlParse } from 'url';
2016-06-22 21:17:48 +03:00
export class SpecManager {
public _schema: any = {};
2016-06-13 20:54:24 +03:00
public apiUrl: string;
private _instance: any;
private _url: string;
2016-06-12 20:44:34 +03:00
2016-06-13 20:54:24 +03:00
static instance() {
2016-06-22 21:17:48 +03:00
return new SpecManager();
2016-06-13 20:54:24 +03:00
}
constructor() {
2016-06-22 21:17:48 +03:00
if (SpecManager.prototype._instance) {
return SpecManager.prototype._instance;
}
2016-06-22 21:17:48 +03:00
SpecManager.prototype._instance = this;
}
load(url) {
let promise = new Promise((resolve, reject) => {
this._schema = {};
JsonSchemaRefParser.bundle(url, {http: {withCredentials: false}})
2016-06-22 21:17:48 +03:00
.then(schema => {
this._url = url;
2016-06-22 21:17:48 +03:00
this._schema = schema;
resolve(this._schema);
this.init();
}, err => reject(err));
});
return promise;
}
2015-11-17 01:03:09 +03:00
/* calculate common used values */
init() {
let urlParts = this._url ? urlParse(this._url) : {};
let schemes = this._schema.schemes;
let protocol;
if (!schemes || !schemes.length) {
// url parser incudles ':' in protocol so remove it
protocol = urlParts.protocol ? urlParts.protocol.slice(0, -1) : 'http';
} else {
protocol = schemes[0];
if (protocol === 'http' && schemes.indexOf('https') >= 0) {
protocol = 'https';
}
}
let host = this._schema.host || urlParts.host;
this.apiUrl = protocol + '://' + host + this._schema.basePath;
2015-11-29 18:14:18 +03:00
if (this.apiUrl.endsWith('/')) {
this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1);
}
this.preprocess();
}
preprocess() {
this._schema.info['x-redoc-html-description'] = renderMd( this._schema.info.description, {
open: (tokens, idx) => {
let content = tokens[idx + 1].content;
safePush(this._schema.info, 'x-redoc-markdown-headers', content);
2016-07-26 12:03:15 +03:00
content = slugify(content);
return `<h${tokens[idx].hLevel} section="section/${content}">` +
`<a class="share-link" href="#section/${content}"></a>`;
},
close: (tokens, idx) => {
return `</h${tokens[idx].hLevel}>`;
}
});
2015-11-17 01:03:09 +03:00
}
get schema() {
return this._schema;
}
2015-10-08 23:21:51 +03:00
byPointer(pointer) {
2015-10-10 16:01:41 +03:00
let res = null;
try {
2016-01-17 00:25:12 +03:00
res = JsonPointer.get(this._schema, decodeURIComponent(pointer));
2015-10-10 16:01:41 +03:00
} catch(e) {/*skip*/ }
return res;
}
2015-10-08 23:21:51 +03:00
2015-10-25 14:26:38 +03:00
resolveRefs(obj) {
Object.keys(obj).forEach(key => {
if (obj[key].$ref) {
2015-12-12 18:29:50 +03:00
let resolved = this.byPointer(obj[key].$ref);
resolved._pointer = obj[key].$ref;
obj[key] = resolved;
2015-10-25 14:26:38 +03:00
}
});
return obj;
}
getMethodParams(methodPtr, resolveRefs) {
/* inject JsonPointer into array elements */
function injectPointers(array, root) {
2015-12-14 16:53:22 +03:00
if (!Array.isArray(array)) {
throw new Error(`parameters must be an array. Got ${typeof array} at ${root}`);
}
2015-10-25 14:26:38 +03:00
return array.map((element, idx) => {
element._pointer = JsonPointer.join(root, idx);
return element;
});
}
2015-12-12 18:29:50 +03:00
// accept pointer directly to parameters as well
if (JsonPointer.baseName(methodPtr) === 'parameters') {
methodPtr = JsonPointer.dirName(methodPtr);
2015-10-25 14:26:38 +03:00
}
2015-12-12 18:29:50 +03:00
//get path params
let pathParamsPtr = JsonPointer.join(JsonPointer.dirName(methodPtr), ['parameters']);
let pathParams = this.byPointer(pathParamsPtr) || [];
let methodParamsPtr = JsonPointer.join(methodPtr, ['parameters']);
let methodParams = this.byPointer(methodParamsPtr) || [];
pathParams = injectPointers(pathParams, pathParamsPtr);
methodParams = injectPointers(methodParams, methodParamsPtr);
2015-10-25 14:26:38 +03:00
if (resolveRefs) {
methodParams = this.resolveRefs(methodParams);
2015-11-04 23:39:46 +03:00
pathParams = this.resolveRefs(pathParams);
2015-10-25 14:26:38 +03:00
}
return methodParams.concat(pathParams);
}
2015-11-17 02:34:13 +03:00
getTagsMap() {
let tags = this._schema.tags || [];
var tagsMap = {};
for (let tag of tags) {
tagsMap[tag.name] = {
description: tag.description,
2015-12-12 14:49:22 +03:00
'x-traitTag': tag['x-traitTag'] || false
2015-11-17 02:34:13 +03:00
};
}
return tagsMap;
}
2016-01-09 17:52:24 +03:00
findDerivedDefinitions(defPointer) {
let definition = this.byPointer(defPointer);
2016-01-15 23:42:55 +03:00
if (!definition) throw new Error(`Can't load schema at ${defPointer}`);
2016-01-09 17:52:24 +03:00
if (!definition.discriminator) return [];
let globalDefs = this._schema.definitions || {};
let res = [];
for (let defName of Object.keys(globalDefs)) {
2016-06-22 19:13:57 +03:00
if (!globalDefs[defName].allOf &&
!globalDefs[defName]['x-derived-from']) continue;
let subTypes = globalDefs[defName]['x-derived-from'] ||
globalDefs[defName].allOf.map(subType => subType._pointer || subType.$ref);
2016-06-22 19:13:57 +03:00
let idx = subTypes.findIndex(ref => ref === defPointer);
2016-01-09 17:52:24 +03:00
if (idx < 0) continue;
2016-04-13 15:46:41 +03:00
let empty = false;
if (subTypes.length === 1) {
empty = true;
}
res.push({name: defName, $ref: `#/definitions/${defName}`, empty});
2016-01-09 17:52:24 +03:00
}
return res;
}
}