For quite some time I have been building out a rules engine for my upcoming saas product AppDoctor. The idea is, when given a dynamic object of data, allow users to define rules to trigger certain actions. I ended up building my own DSL to expose a standard set of elixir functions. Doing it this way I can allow only safe functions to run. From there I compare the AST of a string of bools that are from the result of these comparisons. Finally I squish down the nested bools string to a single value. I did this to allow complex nesting of comparisons.
Here is an example of the transformations at each step.
After some time asking around about how to parse my complex string of bools I ended up with the following code that is short and sweet thanks to elixir being awesome:
The above string just parses for or,and,true and false and recursively calls itself until it resolves to a final bool value. This is nice because I can safely ignore all unsafe things that may be inside the string by not needing to execute it.
With the above DSL I had a pretty good thing going for my rule engine. You could pass a defined set of comparisons for an object at any nested complexity…but yet it still felt restrictive.
In a couple of the conversations I had around the above process people kept bringing up the idea of using Lua to allow users to write their own code. Obviously when I hear about letting users write their own code that I execute on my server, I get a shiver down my spine at all the things that could go wrong. It turns out that lua is easy to safely sandbox and can interface with elixir really well( I actual used an erlang lib though )!
Using the library luerl over the last weekend I was able to get a pretty good first go of safe arbitrary execution together to do some of the things I will need. I had 2 requirements. I needed the Lua script to have access to the object they will define rules against and I needed the Lua script to be able to call a set function that will interface and run some elixir code.
The code ended up as follows:
Admittedly it took a little longer than it probably should have to get to this point due to me not being familiar with erlang syntax.
The first thing to note above is that I am using luerl_sandbox to get a sandboxed instance initiated of lua. This currently is not in production of luerl so I forked the repo and am using the develop branch.
Honestly the code above is not really that interesting other then the functions being set to be used from Lua. I first defined a get function using a closure for the specific rules object so that any time a user called get(“val”) it would pull from their object. The actual get function does string splitting on “.” so that you can access nested values in the object the rule is acting against. I assigned this function to the specific instance of lua using the set_table1 function.
I then defined a function the same as above minus being in a closure. This function is specifically used to get values back from the lua script to the elixir(erlang) world.
While right now it is doing a try rescue to make sure the code returns back a true or false value, I will most likely add it to return a formatting error so that I can have users know when the syntax for the lua script provided is invalid.
I plan to add quite a few other precautions(memory consumption/reduction counting and script timeout) but overall really like the result. AppDoctor will have a lot more use cases thanks to allowing lua scripting. Here are some basic tests of what a rule might look like:
I will probably use a combination of my DSL and lua to provide rules for AppDoctor when it is completed as I don’t think its the best idea to require everyone to know Lua to write rules. Overall I am very happy with how it turned out.