0

I have a situation where I need to store, retrieve, and process user-defined conditions about a data context (an object), determining whether or not the context meets the user's criteria at some point in the future.

LogicJS seems like a bit much complexity for what I need and also doesn't clearly resolve my need to store and retrieve the conditions.

SO?: How can I serialize a function in JavaScript? (tldr; function.toString() and eval()) seems like it would work, but it also looks like a serious security hole.

Does a general solution for this exist?

Community
  • 1
  • 1
Stephen T. Robbins
  • 105
  • 1
  • 1
  • 8
  • How are users defining their conditions? Are they writing JavaScript or are they interacting with UI and you are generating the predicates from their input? – VLAZ Sep 10 '16 at 18:36
  • Couldn't you use a general check(object, constraints) function in which your constraints are a user generated object that allow to test for object properties and values ? For example contraints = {propertyOne: ["string", ["a", "b", "orC"]], propertyTwo: [variableType, arrayOfValues]}. – Albert James Teddy Sep 10 '16 at 18:45
  • @AlbertJamesTeddy that is what I was driving at. I imagine that users would fiddle with some UI to generate the predicate. If that's how it's done, then, by definition, there would be some means of taking the user input (which would essentially be expressed as a bunch of values) and turning it into predicate. From there, it turns the problem from "how do I store this function" to "how do I store these values" as all you need is to replay the values against the predicate generation to get the same predicate. With that in mind, it's trivial to solve. – VLAZ Sep 10 '16 at 19:00
  • @AlbertJamesTeddy That is what I looked at first, using the Lodash functions' predicate iteratee shorthand for _.matches() and a Lodash-style path (string or array) to use with _.get() to store conditions targeted deep into the context. – Stephen T. Robbins Sep 12 '16 at 15:00
  • @vlaz Users are interacting with a UI which I am building. I have cobbled together something basic, but thought that someone smarter than me might have built something good to reuse at some point. – Stephen T. Robbins Sep 12 '16 at 15:01

1 Answers1

1

The solution I'm using is as @vlaz and @AlbertJamesTeddy were suggesting. I could not find an existing, generalized solution.

As of 2016/09/12:

_.mixin({
  evaluatePredicateDescriptor: function (context, descriptor) {
    // A predicate descriptor must be an object with a valid 'op' String property, an appropriate 'arg' property value, and can optionally have a 'path' property.
    // e.g.
    // var egContext = [ [1, 2, 3], [2, 4, 6], [3, 6, 9, 12, 15] ];
    // var egDescriptor = { op: 'any', arg: { path: 'length', op: 'gt', arg: 6 } };
    // var result = _.evaluatePredicateDescriptor(egContext, egDescriptor); // false
    // egDescriptor = { op: 'any', arg: { op: 'any', arg: { op: 'equals', arg: 15 } } };
    // result = _.evaluatePredicateDescriptor(egContext, egDescriptor); // true
    if (this.descriptor === 'true' || this.descriptor === true) { return true; }
    if (this.descriptor === 'false' || this.descriptor === false) { return false; }
    if (!_.isEmpty(descriptor.path)) {
      context = _.get(context, descriptor.path)
    }
    switch (descriptor.op) {
      case 'true':
        return true;
        break;
      case 'false':
        return false;
        break;
      case 'and':
        return _.every(descriptor.arg, function (childDescriptor) {
          return _.evaluatePredicateDescriptor(context, childDescriptor);
        });
        break;
      case 'or':
        return _.some(descriptor.arg, function (childDescriptor) {
          return _.evaluatePredicateDescriptor(context, childDescriptor);
        });
      case 'not':
        return !_.evaluatePredicateDescriptor(context, descriptor.arg);
        break;
      case 'all':
        return _.every(context, function (contextArrayElement) {
          return _.evaluatePredicateDescriptor(contextArrayElement, descriptor.arg);
        });
        break;
      case 'any':
        return _.some(context, function (contextArrayElement) {
          return _.evaluatePredicateDescriptor(contextArrayElement, descriptor.arg);
        });
        break;
      case 'every':
        return _.every(context, descriptor.arg);
        break;
      case 'some':
        return _.some(context, descriptor.arg);
        break;
      case 'lt':
        return _.lt(context, descriptor.arg);
        break;
      case 'lte':
        return _.lte(context, descriptor.arg);
        break;
      case 'equals':
        return _.isEqual(context, descriptor.arg);
        break;
      case 'gte':
        return _.gte(context, descriptor.arg);
        break;
      case 'gt':
        return _.gt(context, descriptor.arg);
        break;
      case 'matches':
        return _.isMatch(context, descriptor.arg);
        break;
      case 'regex':
        var i = descriptor.arg.lastIndexOf('/');
        var pattern = descriptor.arg.substring(1, i);
        var flags = descriptor.arg.substring(i + 1);
        var regex = new RegExp(pattern, flags);
        return regex.test(context);
        break;
      default:
        console.warn('_.evaluatePredicateDescriptor() processed a descriptor without a valid operation.');
        break;
    }
    return false;
  }
});

https://github.com/stvrbbns/my-extended-lodash/blob/master/my-extended-lodash.js

Stephen T. Robbins
  • 105
  • 1
  • 1
  • 8