devebot
Version:
Nodejs Microservice Framework
348 lines (347 loc) • 10.3 kB
JavaScript
"use strict";
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const lodash = require("lodash");
const chores = require("../utils/chores");
const blockRef = chores.getBlockRef(__filename);
const _isUpgradeSupported = chores.isUpgradeSupported.bind(chores);
function MappingLoader(params = {}) {
const loggingFactory = params.loggingFactory.branch(blockRef);
const L = loggingFactory.getLogger();
const T = loggingFactory.getTracer();
const {
schemaValidator
} = params;
assert.ok(this.constructor === MappingLoader);
L && L.has("silly") && L.log("silly", T && T.toMessage({
tags: [blockRef, "constructor-begin"],
text: " + constructor start ..."
}));
this.loadMappings = function (mappingStore, options = {}) {
// determine the loaderContext
const loaderContext = lodash.get(options, "context", {});
let mappings = {};
if (lodash.isString(mappingStore)) {
const store = {};
store[chores.getUUID()] = mappingStore;
mappingStore = store;
}
if (mappingStore && lodash.isObject(mappingStore)) {
lodash.forOwn(mappingStore, function (descriptor, bundle) {
L && L.has("debug") && L.log("debug", T && T.add({
bundle,
enabled: descriptor && descriptor.enabled !== false
}).toMessage({
tags: [blockRef, "load-mappings"],
text: " - load the mappings bundle [${bundle}] - enable: ${enabled}"
}));
if (lodash.isString(descriptor)) {
descriptor = {
location: descriptor
};
}
if (descriptor && descriptor.enabled === false) {
return;
}
//
lodash.defaults(descriptor, lodash.pick(options, ["evaluated", "fileFilter", "keyGenerator"]));
//
if (_isUpgradeSupported("validate-mapping-descriptor")) {
const result = validateDescriptor({
schemaValidator
}, descriptor);
if (result && result.valid === false) {
throw new Error("mapping-descriptor is invalid");
}
}
//
let accum = loadMappingStore(bundle, descriptor, loaderContext);
lodash.merge(mappings, accum);
});
}
// validate with a jsonschema
const jsonschema = lodash.get(options, "jsonschema", {});
if (!lodash.isEmpty(jsonschema)) {
const result = schemaValidator.validate(mappings, jsonschema);
if (result && result.valid === false) {
throw new Error("mappings is invalid with the jsonschema");
}
}
// validate with a validator
const validator = lodash.get(options, "validator", {});
if (lodash.isFunction(validator)) {
const result = validator(mappings);
if (result && result.ok === false) {
throw new Error("mappings is invalid with the validator");
}
}
return mappings;
};
L && L.has("silly") && L.log("silly", T && T.toMessage({
tags: [blockRef, "constructor-end"],
text: " - constructor has finished"
}));
}
MappingLoader.argumentSchema = {
"$id": "mappingLoader",
"type": "object",
"properties": {
"appName": {
"type": "string"
},
"appInfo": {
"type": "object"
},
"sandboxConfig": {
"type": "object"
},
"profileConfig": {
"type": "object"
},
"loggingFactory": {
"type": "object"
},
"schemaValidator": {
"type": "object"
}
}
};
if (_isUpgradeSupported("builtin-mapping-loader")) {
lodash.assign(MappingLoader.argumentSchema.properties, {
"issueInspector": {
"type": "object"
}
});
}
if (_isUpgradeSupported("sandbox-mapping-loader")) {
lodash.assign(MappingLoader.argumentSchema.properties, {
"sandboxRegistry": {
"type": "object"
}
});
}
MappingLoader.MAPPING_STORE_SCHEMA = {
"type": "object",
"patternProperties": {
"^.+$": {
"oneOf": [{
"type": "string"
}, {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"location": {
"type": "string"
},
"evaluated": {
"type": "boolean"
},
"fileFilter": {
"description": "A customized filter that select the mapping files"
},
"keyGenerator": {
"description": "A customized mapping key generation function"
}
},
"additionalProperties": false
}]
}
}
};
module.exports = MappingLoader;
function validateDescriptor(ctx = {}, scriptObject = {}) {
const {
schemaValidator
} = ctx;
const results = [];
results.push(schemaValidator.validate(scriptObject, MappingLoader.MAPPING_STORE_SCHEMA));
if (scriptObject.fileFilter != null && !lodash.isFunction(scriptObject.fileFilter)) {
results.push({
valid: false,
errors: [{
message: "fileFilter has wrong type: " + typeof scriptObject.fileFilter
}]
});
}
if (scriptObject.keyGenerator != null && !lodash.isFunction(scriptObject.keyGenerator)) {
results.push({
valid: false,
errors: [{
message: "keyGenerator has wrong type: " + typeof scriptObject.keyGenerator
}]
});
}
return results.reduce(function (output, result) {
output.valid = output.valid && result.valid !== false;
output.errors = output.errors.concat(result.errors);
return output;
}, {
valid: true,
errors: []
});
}
function loadMappingStore(mappingName, {
location,
fileFilter,
keyGenerator,
evaluated
}, loaderContext) {
if (!lodash.isFunction(fileFilter)) {
fileFilter = defaultFileFilter;
}
if (!lodash.isFunction(keyGenerator)) {
keyGenerator = defaultKeyGenerator;
}
let mappings = {};
const mappingStat = getMappingFileStat(location);
//
if (mappingStat.isFile()) {
const fileInfo = lodash.assign(path.parse(location), {
standalone: true
});
loadMappingFile(mappings, mappingName, keyGenerator, evaluated, fileInfo, loaderContext);
}
if (mappingStat.isDirectory()) {
let multifiles = true;
try {
const indexFile = path.join(location, "index.js");
const indexStat = fs.statSync(indexFile);
if (indexStat.isFile()) {
const fileInfo = lodash.assign(path.parse(indexFile), {
standalone: true
});
loadMappingFile(mappings, mappingName, keyGenerator, evaluated, fileInfo, loaderContext);
multifiles = false;
}
} catch (err) {
multifiles = true;
}
if (multifiles) {
const fileInfos = traverseDir(location, fileFilter);
lodash.forEach(fileInfos, function (fileInfo) {
loadMappingFile(mappings, mappingName, keyGenerator, evaluated, fileInfo, loaderContext);
});
}
}
return mappings;
}
function getMappingFileStat(location) {
let mappingStat;
try {
mappingStat = fs.statSync(location);
} catch (err) {
location = location + ".js";
try {
mappingStat = fs.statSync(location);
} catch (e__) {
throw err;
}
}
return mappingStat;
}
function loadMappingFile(mappings, mappingName, keyGenerator, evaluated, fileInfo, loaderContext) {
const mappingFile = getFilePath(fileInfo);
const mappingBody = evaluateMappingFile(mappingFile, mappingName, evaluated, loaderContext);
const mappingId = keyGenerator(mappingName, fileInfo, mappingBody);
if (lodash.isEmpty(mappingId)) {
if (lodash.isObject(mappingBody)) {
lodash.assign(mappings, mappingBody);
}
} else {
mappings[mappingId] = mappingBody;
}
}
function getFilePath(fileInfo) {
return path.join(fileInfo.dir, fileInfo.base);
}
function defaultFileFilter(fileInfo) {
return fileInfo.ext === ".js";
}
function defaultKeyGenerator(mappingName, fileInfo, fileBody) {
return mappingName;
}
function evaluateMappingFile(mappingPath, mappingName, evaluated, loaderContext) {
const mappingScript = requireMappingFile(mappingPath);
if (lodash.isFunction(mappingScript) && evaluated !== false) {
try {
return mappingScript(mappingName, loaderContext);
} catch (err) {
throw err;
}
}
return mappingScript;
}
function requireMappingFile(mappingFile) {
try {
return require(mappingFile);
} catch (err) {
return null;
}
}
function traverseDir(dir, filter, fileInfos) {
if (!lodash.isFunction(filter)) {
let exts = filter;
if (exts != null) {
if (lodash.isRegExp(exts)) {
filter = function (fileInfo) {
if (fileInfo == null) return true;
return exts.test(path.join(fileInfo.path, fileInfo.base));
};
} else {
if (!lodash.isArray(exts)) {
exts = [exts];
}
filter = function (fileInfo) {
if (fileInfo == null) return true;
for (const i in exts) {
const ext = exts[i];
const filepath = path.join(fileInfo.path, fileInfo.base);
if (filepath.indexOf(ext.toString()) >= 0) {
return true;
}
}
return false;
};
}
}
}
if (!lodash.isArray(fileInfos)) {
fileInfos = [];
}
try {
dir = path.normalize(dir);
if (dir && dir !== path.sep && dir.length > 1 && dir.endsWith(path.sep)) {
dir = dir.substring(0, dir.length - 1);
}
return traverseDirRecursively(dir, dir, filter, fileInfos);
} catch (err) {
return fileInfos;
}
}
function traverseDirRecursively(homeDir, dir, filter, fileInfos = []) {
assert.ok(filter == null || lodash.isFunction(filter));
const files = fs.readdirSync(dir);
for (const i in files) {
const filename = path.join(dir, files[i]);
const filestat = fs.statSync(filename);
if (filestat.isDirectory()) {
traverseDirRecursively(homeDir, filename, filter, fileInfos);
} else if (filestat.isFile()) {
const fileInfo = path.parse(filename);
if (filter == null || filter(fileInfo)) {
fileInfos.push({
home: homeDir,
path: fileInfo.dir.slice(homeDir.length),
dir: fileInfo.dir,
base: fileInfo.base,
name: fileInfo.name,
ext: fileInfo.ext
});
}
}
}
return fileInfos;
}