qhwa

qhwa

Formular - A tiny DSL engine / code evaluator (configuration as code)

Hi, all,

I just published Formular package. It is a tiny library that evaluates a piece of Elixir code.

Online documentation

On the shoulder of Elixir’s Code module

Given a piece of Elixir code (as a string, or AST), Formular runs it with Elixir’s Code module under some security limitations.

So far, the limitations are:

  • No calling module functions;
  • No calling exit;
  • No sending messages.

Indeed, the whole library consists of only one thin module, thanks to the power Elixir has already shipped out of the box.

Motivation

Formular was developed to support some dynamic configuration scenarios. For example, in a scene of an online book store, the discount of a book can be dynamically configured as a piece of code, then evaluated by Formular:

iex> discount_formula = ~s"
...>   case order do
...>     # old books get a big promotion
...>     %{book: %{year: year}} when year < 2000 ->
...>       0.5
...>   
...>     %{book: %{tags: tags}} ->
...>       # Elixir books!
...>       if ~s{elixir} in tags do
...>         0.9
...>       else
...>         1.0
...>       end
...>
...>     _ ->
...>       1.0
...>   end
...> "
...>
...> book_order = %{
...>   book: %{
...>     title: "Elixir in Action", year: 2019, tags: ["elixir"]
...>   }
...> }
...>
...> Formular.eval(discount_formula, [order: book_order])
{:ok, 0.9}

In such a way, the discount calculation code, which changes frequently, is separated away from the stable business flow, and the primary code is probably more generic and flexible.

I’ve been using it in production for a while so I publish it today in case others may find it useful too.

Cheers!

Most Liked Responses

hauleth

hauleth

DoS (atom exhaustion):

Formular.eval(~S|for a <- %Range{first: 0, last: 100_000, step: 1}, do: :"#{a}"|, [])

I needed to create range manually, as you do not export ../2 operator.

qhwa

qhwa

Two new versions have been released:

  • v0.2.2

    • brings performance improvements to 0.2.x
  • v0.3.0

    • allows limiting the execution time and heap size
    • allows compiling a code string to an Elixir module which brings more performance improvements,
    • adds new API Formula.used_vars/2 to extract the used variable names in the formula.

Highlights:

Performance improvements:

A simple benchmark on a simple formula results in this:

Name                      ips        average  deviation         median         99th %
compiled_module      947.84 K        1.06 μs  ±5536.01%        0.83 μs        1.45 μs
eval                  14.53 K       68.82 μs     ±8.93%       68.69 μs       87.93 μs
eval_ast               4.86 K      205.58 μs    ±18.43%      193.89 μs      333.89 μs

Comparison: 
compiled_module      947.84 K
eval                  14.53 K - 65.23x slower +67.76 μs
eval_ast               4.86 K - 194.85x slower +204.52 μs

The compilation approach is about 80x faster than the original eval approach in v0.2.2, and 300x faster than versions prior to v0.2.2.

Restricted evaluation:

  • limited execution time:

    Formular.eval(code, timeout: :timer.seconds(5))
    
  • limited max heap size by word:

    Formular.eval(code, max_heap_size: 15_000)
    

If limited, the evaluation will be run in a separate process.

Extract used vars API:

This can be helpful if you need to build some UI arround the formula.

iex> Formular.used_vars("a + b - 1")
[:a, :b]
mat-hek

mat-hek

Membrane Core Team

Nice, though I’m curious if you tried using Sand?

Where Next?

Popular in Announcing Top

mspanc
I am pleased to announce an initial release of the Membrane Framework - an Elixir-based framework with special focus on processing multim...
New
Crowdhailer
I have been updating a library that allows you to pipe between functions that use the erlang result tuple convention. Assuming you have ...
New
mbuhot
Leverage Open Api 3.0 (Swagger) to document, test, validate and explore your Plug and Phoenix APIs. Generate and serve a JSON Open API ...
New
sbs
Only 650 LOC, wrote for fun :slight_smile: https://github.com/sunboshan/qrcode
New
Azolo
Hey everyone, I just released WebSockex which is a Elixir WebSocket client. WebSockex strives to work as a OTP special process, be RFC6...
New
hpopp
After just over two years in development, this latest version of Pigeon is what I finally consider done in regards to my original vision ...
New
benlime
LiveMotion enables high performance animations declared on the server and run on the client. As a follow up to my previous thread A libr...
New
scohen
Lexical Lexical is a next-generation language server for the Elixir programming language. Features Context aware code completion As-you...
New
wfgilman
I’ve cleaned up and open sourced three financial libraries I was using for my company. They are bindings for the APIs of these three comp...
New
trisolaran
Hi! :waving_hand: I would like to present LiveSelect, a little library that I wrote to easily add a dynamic selection input to your LV f...
198 10858 107
New

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New

We're in Beta

About us Mission Statement