You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

127 lines
3.4 KiB
JavaScript

"use strict";
const Promise = require("bluebird");
const objectFromEntries = require("object.fromentries");
const util = require("util");
function resolveFromDataSource(dataContext, dataSource, id) {
if (dataContext[dataSource] != null) {
return dataContext[dataSource].load(id);
} else {
throw new Error(`Specified data source '${dataSource}' does not exist`);
}
}
function withProperty(dataSource, id, property) {
return withData(dataSource, id, (value) => {
return value[property];
});
}
function withData(dataSource, id, callback) {
return function (args, context) {
let { data } = context;
return Promise.try(() => {
return resolveFromDataSource(data, dataSource, id);
}).then((value) => {
if (value != null) {
// FIXME: Inject 'properties'
return callback(value, args, context);
} else {
// QUESTION: Why do we disallow this again?
throw new Error(`Got a null-ish value from data source '${dataSource}' for ID '${util.inspect(id)}'`);
}
});
};
}
function withDynamicHandler(handler, object) {
return function (args, context) {
let { data } = context;
function resolveProperty(property, fromObject = object) {
if (typeof fromObject[property] !== "function") {
throw new Error(`FIXME: Properties can apparently be non-functions`);
}
return fromObject[property](args, context);
}
let extendedContext = {
... context,
resolveProperty: resolveProperty,
resolveProperties: function (properties, fromObject) {
return Promise.map(properties, (property) => {
return Promise.try(() => {
return resolveProperty(property, fromObject);
}).then((value) => {
return [ property, value ];
});
}).then((entries) => {
return objectFromEntries(entries);
});
},
resolvePropertyPath: function (propertyPath, fromObject) {
let initialObject = fromObject ?? object;
return Promise.reduce(propertyPath, (last, property) => {
if (last != null) {
return resolveProperty(property, last);
}
}, initialObject);
},
resolveDataSource: function (dataSource, id) {
return resolveFromDataSource(data, dataSource, id);
}
};
return handler(args, extendedContext);
};
}
let ID = Symbol("ID");
let LocalProperties = Symbol("LocalProperties");
let Dynamic = Symbol("Dynamic");
module.exports = {
ID: ID,
Dynamic: Dynamic,
LocalProperties: LocalProperties,
createDataObject: function createDataObject(mappings) {
let object = {};
if (mappings[LocalProperties] != null) {
Object.assign(object, mappings[LocalProperties]);
}
if (mappings[Dynamic] != null) {
for (let [property, handler] of Object.entries(mappings[Dynamic])) {
object[property] = withDynamicHandler(handler, object);
}
}
for (let [dataSource, items] of Object.entries(mappings)) {
if (items[ID] != null) {
let id = items[ID];
for (let [property, source] of Object.entries(items)) {
if (object[property] == null) {
if (typeof source === "string") {
object[property] = withProperty(dataSource, id, source);
} else if (typeof source === "function") {
object[property] = withData(dataSource, id, source);
} /* FIXME: else */
} else {
throw new Error(`Handler already defined for property '${property}' - maybe you specified it twice for different data sources?`);
}
}
} else {
throw new Error(`No object ID was provided for the '${dataSource}' data source`);
}
}
return object;
}
};