119 lines
2.8 KiB
JavaScript
119 lines
2.8 KiB
JavaScript
const path = require("path");
|
|
const fs = require("fs");
|
|
const request = require("sync-request");
|
|
|
|
function readJsonStringFromFile(path) {
|
|
try {
|
|
const result = fs.readFileSync(path, "utf-8");
|
|
return result;
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
}
|
|
function readJsonStringFromUrl(url) {
|
|
try {
|
|
const res = request("GET", url);
|
|
return res.getBody("utf-8");
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
}
|
|
function readJsonString(pathOrUrl) {
|
|
if (pathOrUrl.startsWith("http")) {
|
|
return readJsonStringFromUrl(pathOrUrl);
|
|
}
|
|
return readJsonStringFromFile(pathOrUrl);
|
|
}
|
|
|
|
class SchemaCache {
|
|
constructor(schemaPaths, extensionPaths) {
|
|
this.schemaPaths = schemaPaths;
|
|
this.extensionSchemaPaths = extensionPaths;
|
|
this.cache = {};
|
|
this.contextStack = [];
|
|
}
|
|
|
|
load(name) {
|
|
// First search relative to the current context.
|
|
// If not found, search the schema paths.
|
|
const searchPaths = [
|
|
this.resolveRelativePath(name),
|
|
...this.schemaPaths.map((path) => this.resolvePath(path, name)),
|
|
];
|
|
return this.loadFromSearchPaths(name, searchPaths);
|
|
}
|
|
|
|
loadExtension(schemaName, extensionName) {
|
|
const searchPaths = this.extensionSchemaPaths.map((path) =>
|
|
this.resolvePath(path, schemaName)
|
|
);
|
|
// Prioritize paths that contain the extension name because multiple extensions may have the same schema name.
|
|
searchPaths.sort((pathA, pathB) => {
|
|
const resultA = pathA.indexOf(extensionName) === -1 ? 0 : 1;
|
|
const resultB = pathB.indexOf(extensionName) === -1 ? 0 : 1;
|
|
return resultB - resultA;
|
|
});
|
|
return this.loadFromSearchPaths(schemaName, searchPaths);
|
|
}
|
|
|
|
loadFromSearchPaths(name, paths) {
|
|
let jsonString;
|
|
|
|
let path;
|
|
for (path of paths) {
|
|
const existingSchema = this.cache[path];
|
|
if (existingSchema) {
|
|
return existingSchema;
|
|
}
|
|
|
|
jsonString = readJsonString(path);
|
|
|
|
if (jsonString) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (jsonString === undefined) {
|
|
console.warn(
|
|
`Could not resolve ${name}. Tried:\n${paths
|
|
.map((path) => ` * ${path}`)
|
|
.join("\n")}`
|
|
);
|
|
return undefined;
|
|
}
|
|
|
|
const result = JSON.parse(jsonString);
|
|
result.sourcePath = path;
|
|
this.cache[path] = result;
|
|
|
|
return result;
|
|
}
|
|
|
|
pushContext(schema) {
|
|
this.contextStack.push(schema);
|
|
}
|
|
|
|
popContext() {
|
|
this.contextStack.pop();
|
|
}
|
|
|
|
resolveRelativePath(name) {
|
|
if (this.contextStack.length === 0) {
|
|
return name;
|
|
}
|
|
|
|
const base = this.contextStack[this.contextStack.length - 1].sourcePath;
|
|
return this.resolvePath(base, name);
|
|
}
|
|
|
|
resolvePath(base, name) {
|
|
if (base.startsWith("http")) {
|
|
return new URL(name, base).href;
|
|
} else {
|
|
return path.resolve(base, "..", name);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = SchemaCache;
|