redoc/lib/utils/spec-manager.ts

233 lines
7.2 KiB
TypeScript
Raw Normal View History

'use strict';
2016-08-28 21:46:10 +03:00
import * as JsonSchemaRefParser from 'json-schema-ref-parser';
import { JsonPointer } from './JsonPointer';
2016-09-12 22:48:44 +03:00
import { parse as urlParse, resolve as urlResolve } from 'url';
2016-10-23 20:18:42 +03:00
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
2016-10-31 11:15:04 +03:00
import { MdRenderer } from './md-renderer';
2016-10-30 18:56:24 +03:00
2017-01-30 17:11:14 +03:00
import { SwaggerOperation, SwaggerParameter } from './swagger-typings';
function getDiscriminator(obj) {
return obj.discriminator || obj['x-extendedDiscriminator'];
}
2017-01-26 19:21:24 +03:00
export interface DescendantInfo {
$ref: string;
name: string;
active?: boolean;
2017-01-28 16:57:22 +03:00
idx?: number;
2017-01-26 19:21:24 +03:00
}
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;
public basePath: string;
2016-10-23 20:18:42 +03:00
public spec = new BehaviorSubject<any|null>(null);
private _url: string;
private parser: any;
2016-06-12 20:44:34 +03:00
2016-10-23 20:18:42 +03:00
load(urlOrObject: string|Object) {
let promise = new Promise((resolve, reject) => {
this.parser = new JsonSchemaRefParser();
this.parser.bundle(urlOrObject, {http: {withCredentials: false}})
2016-06-22 21:17:48 +03:00
.then(schema => {
2016-10-23 20:18:42 +03:00
if (typeof urlOrObject === 'string') {
this._url = urlOrObject;
}
this._schema = schema;
2016-10-14 11:44:18 +03:00
try {
2016-06-22 21:17:48 +03:00
this.init();
2016-11-23 02:23:32 +03:00
resolve(this._schema);
2016-11-24 16:29:29 +03:00
this.spec.next(this._schema);
2016-10-14 11:44:18 +03:00
} catch(err) {
reject(err);
}
2016-06-22 21:17:48 +03:00
}, err => reject(err));
});
return promise;
}
2015-11-17 01:03:09 +03:00
/* calculate common used values */
init() {
2016-09-12 22:48:44 +03:00
let urlParts = this._url ? urlParse(urlResolve(window.location.href, 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.basePath = this._schema.basePath || '/';
this.apiUrl = protocol + '://' + host + this.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() {
2016-10-30 18:56:24 +03:00
let mdRender = new MdRenderer();
if (!this._schema.info.description) this._schema.info.description = '';
2016-10-31 11:15:04 +03:00
if (this._schema.securityDefinitions) {
let SecurityDefinitions = require('../components/').SecurityDefinitions;
mdRender.addPreprocessor(SecurityDefinitions.insertTagIntoDescription);
}
2016-10-30 18:56:24 +03:00
this._schema.info['x-redoc-html-description'] = mdRender.renderMd(this._schema.info.description);
this._schema.info['x-redoc-markdown-headers'] = mdRender.firstLevelHeadings;
this._schema.info['x-redoc-markdown-subheaders'] = mdRender.secondLevelHeadings;
2015-11-17 01:03:09 +03:00
}
get schema() {
return this._schema;
}
2016-10-23 20:18:42 +03:00
set schema(val:any) {
this._schema = val;
this.spec.next(this._schema);
}
2015-10-08 23:21:51 +03:00
byPointer(pointer) {
2015-10-10 16:01:41 +03:00
let res = null;
if (pointer == undefined) return null;
2015-10-10 16:01:41 +03:00
try {
2016-01-17 00:25:12 +03:00
res = JsonPointer.get(this._schema, decodeURIComponent(pointer));
} catch(e) {
// if resolved from outer files simple jsonpointer.get fails to get correct schema
if (pointer.charAt(0) !== '#') pointer = '#' + pointer;
try {
res = this.parser.$refs.get(decodeURIComponent(pointer));
} catch(e) { /* skip */ }
}
2015-10-10 16:01:41 +03:00
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;
}
2017-01-30 17:11:14 +03:00
getMethodParams(methodPtr:string):SwaggerParameter[] {
2015-10-25 14:26:38 +03:00
/* inject JsonPointer into array elements */
2017-01-30 17:11:14 +03:00
function injectPointers(array:SwaggerParameter[], 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']);
2017-01-30 17:11:14 +03:00
let pathParams:SwaggerParameter[] = this.byPointer(pathParamsPtr) || [];
2015-12-12 18:29:50 +03:00
let methodParamsPtr = JsonPointer.join(methodPtr, ['parameters']);
2017-01-30 17:11:14 +03:00
let methodParams:SwaggerParameter[] = this.byPointer(methodParamsPtr) || [];
2015-12-12 18:29:50 +03:00
pathParams = injectPointers(pathParams, pathParamsPtr);
methodParams = injectPointers(methodParams, methodParamsPtr);
2017-01-30 17:11:14 +03:00
// resolve references
methodParams = this.resolveRefs(methodParams);
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
};
2016-10-31 13:32:03 +03:00
if (tag['x-traitTag']) {
console.warn(`x-traitTag (${tag.name}) is deprecated since v1.5.0 and will be removed in the future`);
}
2015-11-17 02:34:13 +03:00
}
return tagsMap;
}
2017-01-26 19:21:24 +03:00
findDerivedDefinitions(defPointer: string, schema): DescendantInfo[] {
let definition = schema || this.byPointer(defPointer);
2016-01-15 23:42:55 +03:00
if (!definition) throw new Error(`Can't load schema at ${defPointer}`);
if (!definition.discriminator && !definition['x-extendedDiscriminator']) return [];
2016-01-09 17:52:24 +03:00
let globalDefs = this._schema.definitions || {};
let res:DescendantInfo[] = [];
let extendedDiscriminatorProp = definition['x-extendedDiscriminator'];
2016-01-09 17:52:24 +03:00
for (let defName of Object.keys(globalDefs)) {
let def = globalDefs[defName];
if (!def.allOf &&
!def['x-derived-from']) continue;
let subTypes = def['x-derived-from'] ||
def.allOf.map(subType => subType._pointer || subType.$ref);
let pointers;
if (definition['x-derived-from']) {
pointers = [defPointer, ...definition['x-derived-from']];
} else {
pointers = [defPointer];
}
let idx = -1;
for (let ptr of pointers) {
idx = subTypes.findIndex(ref => ptr && ref === ptr);
if (idx >= 0) break;
}
2016-01-09 17:52:24 +03:00
if (idx < 0) continue;
let derivedName = defName;
if (extendedDiscriminatorProp) {
let prop = def.properties && def.properties[extendedDiscriminatorProp];
if (prop && prop.enum && prop.enum.length === 1) {
derivedName = prop.enum[0];
}
}
2017-01-28 16:57:22 +03:00
res.push({name: derivedName, $ref: `#/definitions/${defName}`});
2016-01-09 17:52:24 +03:00
}
return res;
}
2017-01-26 19:21:24 +03:00
getDescendant(descendant:DescendantInfo, componentSchema:any) {
let res;
if (!getDiscriminator(componentSchema) && componentSchema.allOf) {
// discriminator inherited from parents
// only one discriminator and only one level of inheritence is supported at the moment
res = Object.assign({}, componentSchema);
let idx = res.allOf.findIndex(subSpec => !!getDiscriminator(subSpec));
res.allOf[idx] = this.byPointer(descendant.$ref);
} else {
// this.pointer = activeDescendant.$ref;
res = this.byPointer(descendant.$ref);
}
return res;
}
}