ut-tools
Version:
Build and Release management automation package.
424 lines • 19.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const json_1 = require("@stoplight/json");
const path_1 = require("@stoplight/path");
const dependency_graph_1 = require("dependency-graph");
const immer_1 = require("immer");
const get = require("lodash.get");
const set = require("lodash.set");
const URI = require("urijs");
const uri_1 = require("./uri");
const cache_1 = require("./cache");
const crawler_1 = require("./crawler");
const Utils = require("./utils");
const memoize = require('fast-memoize');
let resolveRunnerCount = 0;
exports.defaultGetRef = (key, val) => {
if (val && typeof val === 'object' && typeof val.$ref === 'string')
return val.$ref;
return;
};
class ResolveRunner {
constructor(source, graph = new dependency_graph_1.DepGraph({ circular: true }), opts = {}) {
this.ctx = {};
this.computeRef = (opts) => {
const refStr = this.getRef(opts.key, opts.val);
if (refStr === undefined)
return;
let ref = new uri_1.ExtendedURI(refStr);
if (refStr[0] !== '#') {
const isFile = this.isFile(ref);
if (isFile) {
let absRef = ref.toString();
if (!ref.is('absolute')) {
if (this.baseUri.toString()) {
absRef = path_1.join(path_1.dirname(this.baseUri.toString()), path_1.stripRoot(absRef));
}
else {
absRef = '';
}
}
if (absRef) {
ref = new URI(path_1.toFSPath(absRef)).fragment(ref.fragment());
}
}
else if (ref.scheme().includes('http') || (ref.scheme() === '' && this.baseUri.scheme().includes('http'))) {
if (this.baseUri.authority() !== '' && ref.authority() === '') {
ref = ref.absoluteTo(this.baseUri);
}
}
}
if (this.transformRef) {
return this.transformRef(Object.assign(Object.assign({}, opts), { ref, uri: this.baseUri }), this.ctx);
}
return ref;
};
this.atMaxUriDepth = () => {
return this.uriStack.length >= 100;
};
this.lookupUri = (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
const { ref } = opts;
let scheme = ref.scheme();
if (!this.resolvers[scheme] && this.isFile(ref)) {
scheme = 'file';
}
const resolver = this.resolvers[scheme];
if (!resolver) {
throw new Error(`No resolver defined for scheme '${ref.scheme() || 'file'}' in ref ${ref.toString()}`);
}
let result = yield resolver.resolve(ref, this.ctx);
if (this.parseResolveResult) {
try {
const parsed = yield this.parseResolveResult({
uriResult: result,
result,
targetAuthority: ref,
parentAuthority: this.baseUri,
parentPath: opts.parentPath,
fragment: opts.fragment,
});
result = parsed.result;
}
catch (e) {
throw new Error(`Could not parse remote reference response for '${ref.toString()}' - ${String(e)}`);
}
}
return new ResolveRunner(result, this.graph, {
depth: this.depth + 1,
baseUri: ref.toString(),
root: ref,
uriStack: this.uriStack,
uriCache: this.uriCache,
resolvers: this.resolvers,
transformRef: this.transformRef,
parseResolveResult: this.parseResolveResult,
transformDereferenceResult: this.transformDereferenceResult,
dereferenceRemote: this.dereferenceRemote,
dereferenceInline: this.dereferenceInline,
ctx: this.ctx,
});
});
this.lookupAndResolveUri = (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
const { val, ref, resolvingPointer, parentPointer, pointerStack } = opts;
const parentPath = opts.parentPath ? opts.parentPath.slice() : [];
const uriCacheKey = this.computeUriCacheKey(ref);
const lookupResult = {
uri: ref,
pointerStack,
targetPath: resolvingPointer === parentPointer ? [] : parentPath,
};
if (this.uriStack.includes(uriCacheKey)) {
lookupResult.resolved = {
result: val,
graph: this.graph,
refMap: {},
errors: [],
runner: this,
};
return lookupResult;
}
else {
let uriResolver;
const currentAuthority = this.baseUri.toString();
const newUriStack = currentAuthority && this.depth !== 0 ? currentAuthority : null;
try {
if (this.atMaxUriDepth()) {
throw new Error(`Max uri depth (${this.uriStack.length}) reached. Halting, this is probably a circular loop.`);
}
uriResolver = yield this.lookupUri({
ref: ref.clone().fragment(''),
fragment: ref.fragment(),
cacheKey: uriCacheKey,
parentPath,
});
if (newUriStack) {
uriResolver.uriStack = uriResolver.uriStack.concat(newUriStack);
}
}
catch (e) {
lookupResult.error = {
code: 'RESOLVE_URI',
message: String(e),
uri: ref,
uriStack: newUriStack ? this.uriStack.concat(newUriStack) : this.uriStack,
pointerStack,
path: parentPath,
};
}
if (uriResolver) {
lookupResult.resolved = yield uriResolver.resolve({
jsonPointer: Utils.uriToJSONPointer(ref),
parentPath,
});
if (lookupResult.resolved.errors.length) {
for (const error of lookupResult.resolved.errors) {
if (error.code === 'POINTER_MISSING' &&
error.path.join('/') === ref.fragment().slice(1)) {
const errorPathInResult = ref.fragment
? json_1.trimStart(error.path, json_1.trimStart(ref.fragment(), '/').split('/'))
: error.path;
if (errorPathInResult && errorPathInResult.length) {
set(lookupResult.resolved.result, errorPathInResult, val);
}
else if (lookupResult.resolved.result) {
lookupResult.resolved.result = val;
}
}
}
}
}
}
return lookupResult;
});
this.id = resolveRunnerCount += 1;
this.depth = opts.depth || 0;
this._source = source;
this.resolvers = opts.resolvers || {};
const baseUri = opts.baseUri || '';
let uri = new URI(baseUri || '');
if (this.isFile(uri)) {
uri = new URI(path_1.toFSPath(baseUri));
}
this.baseUri = uri;
this.uriStack = opts.uriStack || [];
this.uriCache = opts.uriCache || new cache_1.Cache();
this.root = (opts.root && opts.root.toString()) || this.baseUri.toString() || 'root';
this.graph = graph;
if (!this.graph.hasNode(this.root)) {
this.graph.addNode(this.root, { refMap: {}, data: this._source });
}
if (this.baseUri && this.depth === 0) {
this.uriCache.set(this.computeUriCacheKey(this.baseUri), this);
}
this.getRef = opts.getRef || exports.defaultGetRef;
this.transformRef = opts.transformRef;
if (this.depth) {
this.dereferenceInline = true;
}
else {
this.dereferenceInline = typeof opts.dereferenceInline !== 'undefined' ? opts.dereferenceInline : true;
}
this.dereferenceRemote = typeof opts.dereferenceRemote !== 'undefined' ? opts.dereferenceRemote : true;
this.parseResolveResult = opts.parseResolveResult;
this.transformDereferenceResult = opts.transformDereferenceResult;
this.ctx = opts.ctx;
this.lookupUri = memoize(this.lookupUri, {
serializer: this._cacheKeySerializer,
cache: {
create: () => {
return this.uriCache;
},
},
});
}
get source() {
return this._source;
}
resolve(opts) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const resolved = {
result: this.source,
graph: this.graph,
refMap: {},
errors: [],
runner: this,
};
let targetPath;
const jsonPointer = opts && opts.jsonPointer && opts.jsonPointer.trim();
if (jsonPointer && jsonPointer !== '#' && jsonPointer !== '#/') {
try {
targetPath = json_1.pointerToPath(jsonPointer);
}
catch (_a) {
resolved.errors.push({
code: 'PARSE_POINTER',
message: `'${jsonPointer}' JSON pointer is invalid`,
uri: this.baseUri,
uriStack: this.uriStack,
pointerStack: [],
path: [],
});
return resolved;
}
resolved.result = get(resolved.result, targetPath);
}
if (resolved.result === void 0) {
resolved.errors.push({
code: 'POINTER_MISSING',
message: `'${jsonPointer}' does not exist @ '${this.baseUri.toString()}'`,
uri: this.baseUri,
uriStack: this.uriStack,
pointerStack: [],
path: targetPath || [],
});
return resolved;
}
const crawler = new crawler_1.ResolveCrawler(this, jsonPointer, resolved);
crawler.computeGraph(resolved.result, targetPath, jsonPointer || '');
let uriResults = [];
if (crawler.resolvers.length) {
uriResults = yield Promise.all(crawler.resolvers);
}
if (uriResults.length) {
for (const r of uriResults) {
let resolvedTargetPath = r.targetPath;
if (!resolvedTargetPath.length)
resolvedTargetPath = targetPath || [];
resolved.refMap[String(this.baseUri.clone().fragment(json_1.pathToPointer(resolvedTargetPath)))] = String(r.uri);
this._setGraphNodeEdge(String(this.root), json_1.pathToPointer(resolvedTargetPath), String(r.uri));
if (r.error) {
resolved.errors.push(r.error);
}
if (!r.resolved)
continue;
if (r.resolved.errors) {
resolved.errors = resolved.errors.concat(r.resolved.errors);
}
if (r.resolved.result === void 0)
continue;
this._source = immer_1.default(this._source, (draft) => {
if (r.resolved) {
if (!resolvedTargetPath.length) {
return r.resolved.result;
}
else {
set(draft, resolvedTargetPath, r.resolved.result);
this._setGraphNodeData(String(r.uri), r.resolved.result);
}
}
});
}
}
if (typeof this._source === 'object') {
if (this.dereferenceInline) {
this._source = immer_1.default(this._source, (draft) => {
let processOrder = [];
try {
processOrder = crawler.pointerGraph.overallOrder();
for (const pointer of processOrder) {
const dependants = crawler.pointerGraph.dependantsOf(pointer);
if (!dependants.length)
continue;
const pointerPath = json_1.pointerToPath(pointer);
const val = pointerPath.length === 0 ? immer_1.original(draft) : get(draft, pointerPath);
for (const dependant of dependants) {
let isCircular;
const dependantPath = json_1.pointerToPath(dependant);
const dependantStems = crawler.pointerStemGraph.dependenciesOf(pointer);
for (const stem of dependantStems) {
if (json_1.startsWith(dependantPath, json_1.pointerToPath(stem))) {
isCircular = true;
break;
}
}
if (isCircular)
continue;
resolved.refMap[json_1.pathToPointer(dependantPath)] = json_1.pathToPointer(pointerPath);
this._setGraphNodeEdge(this.root, json_1.pathToPointer(dependantPath), json_1.pathToPointer(pointerPath));
if (val !== void 0) {
set(draft, dependantPath, val);
this._setGraphNodeData(json_1.pathToPointer(pointerPath), val);
}
else {
resolved.errors.push({
code: 'POINTER_MISSING',
message: `'${pointer}' does not exist`,
path: dependantPath,
uri: this.baseUri,
uriStack: this.uriStack,
pointerStack: [],
});
}
}
}
}
catch (e) {
}
});
}
if (targetPath) {
resolved.result = get(this._source, targetPath);
}
else {
resolved.result = this._source;
}
}
else {
resolved.result = this._source;
}
if (this.transformDereferenceResult) {
const ref = new URI(jsonPointer || '');
try {
const { result, error } = yield this.transformDereferenceResult({
source: this.source,
result: resolved.result,
targetAuthority: ref,
parentAuthority: this.baseUri,
parentPath: opts ? opts.parentPath || [] : [],
fragment: ref.fragment(),
});
resolved.result = result;
if (error) {
throw new Error(`Could not transform dereferenced result for '${ref.toString()}' - ${String(error)}`);
}
}
catch (e) {
resolved.errors.push({
code: 'TRANSFORM_DEREFERENCED',
message: `Error: Could not transform dereferenced result for '${this.baseUri.toString()}${ref.fragment() !== '' ? `#${ref.fragment()}` : ``}' - ${String(e)}`,
uri: ref,
uriStack: this.uriStack,
pointerStack: [],
path: targetPath,
});
}
}
this._setGraphNodeData(this.root, this._source);
return resolved;
});
}
_cacheKeySerializer(sOpts) {
return sOpts && typeof sOpts === 'object' && sOpts.cacheKey ? sOpts.cacheKey : JSON.stringify(arguments);
}
computeUriCacheKey(ref) {
return ref
.clone()
.fragment('')
.toString();
}
isFile(ref) {
const scheme = ref.scheme();
if (scheme === 'file')
return true;
if (!scheme) {
if (ref.toString().charAt(0) === '/')
return true;
if (this.baseUri) {
const uriScheme = this.baseUri.scheme();
return Boolean(!uriScheme || uriScheme === 'file' || !this.resolvers[uriScheme]);
}
}
else if (!this.resolvers[scheme]) {
return true;
}
return false;
}
_setGraphNodeData(nodeId, data) {
if (!this.graph.hasNode(nodeId))
return;
const graphNodeData = this.graph.getNodeData(nodeId) || {};
graphNodeData.data = data;
this.graph.setNodeData(nodeId, graphNodeData);
}
_setGraphNodeEdge(nodeId, fromPointer, toNodeId) {
if (!this.graph.hasNode(nodeId))
return;
const graphNodeData = this.graph.getNodeData(nodeId) || {};
graphNodeData.refMap = graphNodeData.refMap || {};
graphNodeData.refMap[fromPointer] = toNodeId;
this.graph.setNodeData(nodeId, graphNodeData);
}
}
exports.ResolveRunner = ResolveRunner;
//# sourceMappingURL=runner.js.map