const merge = require('ramda/src/merge');
const mergeAll = require('ramda/src/mergeAll');
const $_type = require('./$_type');
module.exports = ({ invariants, errors }) => {
const { Target, Operator, LogicalType } = require('./columns');
/**
* Abstract Predicate type, a Predicate is the union type of CompoundPredicate | ComparisonPredicate
* @typedef {object} Predicate
* @memberof dataclasses
*/
/**
* Abstract Predicate type, a Predicate is the union type of CompoundPredicate | ComparisonPredicate
* @param {Function} type - Predicate subtype function constructor
* @return {dataclasses.Predicate} predicate
* @memberof dataclasses
*/
function Predicate(type) {
return invariants
.PredicateTypeMustBeValid(type.$name, Predicate.Types)
.then(() =>
merge($_type(type.$name), {
/**
* $canBeRemoved specify if the predicate can be removed or not from the Predicates tree
* @type {Boolean}
* @memberof Predicate
*/
$canBeRemoved: true,
})
);
}
/**
* [description]
* @param {dataclasses.Predicate} predicate predicate
* @return {Object} serializable object
*/
Predicate.toJSON = function(predicate) {
if (ComparisonPredicate.is(predicate))
return ComparisonPredicate.toJSON(predicate);
return CompoundPredicate.toJSON(predicate);
};
/**
* @param {object} json json
* @param {object} internalAPI internalAPI
* @return {Promise<Predicate, errors<*>>} Promise
*/
Predicate.fromJSON = function(json, internalAPI) {
if (ComparisonPredicate.isFromJSON(json))
return ComparisonPredicate.fromJSON(json, internalAPI);
if (CompoundPredicate.isFromJSON(json))
return CompoundPredicate.fromJSON(json, internalAPI);
return Promise.reject(new errors.UnknownJSONData());
};
Predicate.Types = {
ComparisonPredicate: 'ComparisonPredicate',
CompoundPredicate: 'CompoundPredicate',
};
/**
* Abstract Predicate type, a Predicate is the union type of CompoundPredicate | ComparisonPredicate
* @typedef {object} ComparisonPredicate
* @param {string} target - unique id for this target
* @param {string} operator - label that will be displayed for this target
* @param {string} argument - the type_id name this target has
* @memberof dataclasses
*/
/**
* A specialized predicate that you use to compare expressions.
* @param {dataclasses.Target} target target
* @param {dataclasses.Operator} operator operator
* @param {*} argument argument
* @return {Promise<dataclasses.ComparisonPredicate>} yield a ComparisonPredicate or a rejected promise
* @memberof dataclasses
*/
function ComparisonPredicate(target, operator, argument = null) {
return Predicate(ComparisonPredicate).then(predicate =>
merge(predicate, {
target,
operator,
argument,
})
);
}
// by pass var. mangling from minify
ComparisonPredicate.$name = Predicate.Types.ComparisonPredicate;
/**
* @param {ComparisonPredicate} predicate predicate
* @return {Object} JSON serializable object
*/
ComparisonPredicate.toJSON = function(predicate) {
return mergeAll([
Target.toJSON(predicate.target),
Operator.toJSON(predicate.operator),
{
argument: predicate.argument,
},
]);
};
/**
* @param {object} json json
* @param {object} internalAPI internalAPI
* @return {Promise<Predicate, errors<*>>} Promise
*/
ComparisonPredicate.fromJSON = function(json, internalAPI) {
return Promise.all([
internalAPI.getTargetById(json.target_id),
internalAPI.getOperatorById(json.operator_id),
]).then(([target, operator]) =>
ComparisonPredicate(target, operator, json.argument)
);
};
/**
* Yield true if `predicate` is a ComparisonPredicate
* @param {dataclasses.Predicate} predicate {@link dataclasses.Predicate}
* @return {Boolean} true if `predicate` is a ComparisonPredicate
* @memberof dataclasses
*/
ComparisonPredicate.is = predicate => {
return (
predicate && predicate.$_type === Predicate.Types.ComparisonPredicate
);
};
/**
* Yield true if `json` seems to be a ComparisonPredicate
* @param {object} json json
* @private
* @return {Boolean} true if json seems to be a ComparisonPredicate
* @memberof dataclasses
*/
ComparisonPredicate.isFromJSON = json => json && json.target_id;
/**
* A specialized predicate that evaluates logical combinations of other predicates.
* @param {dataclasses.LogicalType} logic The predicate logic
* @param {Array<dataclasses.Predicate>} predicates predicates predicates
* @return {Promise<dataclasses.CompoundPredicate>} yield a {@link dataclasses.CompoundPredicate} or a {@link errors.CompoundPredicateMustHaveAtLeastOneSubPredicate} rejected promise
* @memberof dataclasses
*/
function CompoundPredicate(logic, predicates) {
return invariants
.CompoundPredicateMustHaveAtLeastOneSubPredicate(
predicates,
CompoundPredicate
)
.then(() => Predicate(CompoundPredicate))
.then(predicate =>
merge(predicate, {
logic,
predicates,
})
);
}
// by pass var. mangling from minify
CompoundPredicate.$name = Predicate.Types.CompoundPredicate;
/**
* @param {CompoundPredicate} predicate predicate
* @return {Object} JSON serializable object
*/
CompoundPredicate.toJSON = function(predicate) {
return mergeAll([
LogicalType.toJSON(predicate.logic),
{ predicates: predicate.predicates.map(Predicate.toJSON) },
]);
};
/**
* @param {CompoundPredicate} predicate predicate
* @param {object} internalAPI ui-predicate-core internal api object
* @return {Promise<CompoundPredicate, errors<*>>} Promise
*/
CompoundPredicate.fromJSON = function(predicate, internalAPI) {
return invariants
.CompoundPredicateMustHaveAtLeastOneSubPredicate(
predicate.predicates,
CompoundPredicate
)
.then(() => internalAPI.getLogicalTypeById(predicate.logicalType_id))
.then(logicalType =>
Promise.all(
predicate.predicates.map(predicate =>
Predicate.fromJSON(predicate, internalAPI)
)
).then(predicates => CompoundPredicate(logicalType, predicates))
);
};
/**
* Reduce through the predicates tree
* @param {dataclasses.CompoundPredicate} compoundPredicate starter node
* @param {function} f accumulation function, f(acc, predicate, parents)
* @param {T} acc accumulator
* @param {Array} [parents=[]] path to the node, array of parents
* @return {T} yield the accumulator
* @memberof dataclasses
*/
CompoundPredicate.reduce = function(compoundPredicate, f, acc, parents = []) {
const accumulator = f(acc, compoundPredicate, parents);
return compoundPredicate.predicates.reduce((_acc, predicate, i) => {
const _parents = parents.concat([compoundPredicate, [predicate, i]]);
return CompoundPredicate.is(predicate)
? CompoundPredicate.reduce(predicate, f, _acc, _parents)
: f(_acc, predicate, _parents);
}, accumulator);
};
/**
* Walk through the predicates tree
* @param {dataclasses.CompoundPredicate} compoundPredicate starter node
* @param {Function} f(predicate) iterator function
* @return {undefined}
* @memberof dataclasses
*/
CompoundPredicate.forEach = (compoundPredicate, f) => {
CompoundPredicate.reduce(
compoundPredicate,
(_, predicate) => {
f(predicate);
},
null
);
};
/**
* Yield true if `predicate` is a CompoundPredicate
* @param {dataclasses.Predicate} predicate predicate
* @return {Boolean} true if `predicate` is a CompoundPredicate
* @memberof dataclasses
*/
CompoundPredicate.is = predicate =>
predicate && predicate.$_type === Predicate.Types.CompoundPredicate;
/**
* Yield true if `json` seems to be a CompoundPredicate
* @param {object} json json
* @private
* @return {Boolean} true if json seems to be a CompoundPredicate json
* @memberof dataclasses
*/
CompoundPredicate.isFromJSON = json => json && json.logicalType_id;
return {
Predicate,
ComparisonPredicate,
CompoundPredicate,
};
};