{"id":19892138,"url":"https://github.com/maxpert/rues","last_synced_at":"2025-05-02T18:31:37.978Z","repository":{"id":57664074,"uuid":"440990272","full_name":"maxpert/rues","owner":"maxpert","description":"Rule evaluation sidecar","archived":false,"fork":false,"pushed_at":"2024-02-23T08:48:57.000Z","size":174,"stargazers_count":18,"open_issues_count":0,"forks_count":11,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-22T08:17:55.850Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxpert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-22T21:37:01.000Z","updated_at":"2024-05-25T16:40:35.000Z","dependencies_parsed_at":"2024-11-12T18:21:53.446Z","dependency_job_id":"878b4e8d-9c51-4f10-9fc1-fd302ba63495","html_url":"https://github.com/maxpert/rues","commit_stats":{"total_commits":25,"total_committers":2,"mean_commits":12.5,"dds":0.12,"last_synced_commit":"0f31f6eae9351b920c505087d3163ffd5d0e9ff2"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Frues","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Frues/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Frues/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Frues/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxpert","download_url":"https://codeload.github.com/maxpert/rues/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252088508,"owners_count":21692807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T18:21:42.211Z","updated_at":"2025-05-02T18:31:34.571Z","avatar_url":"https://github.com/maxpert.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RuES - Expression Evaluation as Service \n\nRuES is a minimal JMES expression evaluation side-car, that uses [JMESPath](https://jmespath.org/), and it can handle \narbitrary JSON. Which effectively makes it general purpose logical expression evaluation engine, just like \n[some](https://zerosteiner.github.io/rule-engine/) Python libraries that used to evaluate \nlogical expression. This in turn can allow you implement complex stuff like Rule engine, \nRBAC, or Policy engines etc. \n\nHere is what makes RuES special:\n\n - **Lean and Zippy** - Checkout initial benchmarks below. Under `20 MB` with single CPU one will easily do 10K RPS. \n - **Zero restarts** - Add/remove rules on fly by making changes in `rules.hjson` without restarting.\n - **HTTP \u0026 JSON** - Ubiquitous! No custom protocols, no shenanigans.\n - **UNIX philosophy** - Only evaluates rules, no fancy hooks or integrations. Dead simple!\n\n## Why?\n\nA very obvious question to ask might be, why RuES and why not just use a library? RuES can be beneficial in large \nscale scenarios with following benefits:\n\n - **Unified and consistent rules** - No need to deal with library differences, specially in a polyglot stack you\n   won't have to worry about any inconsistencies, performance issues, or library maintenances.\n - **Isolated and scalable** - While embedded libraries can have a broader attack surface the isolated process\n   gives you sandbox, and due to being lightweight have it as a sidecar giving you sub-millisecond latencies. \n   This not only allows developers to hand off security to the right team, but also allows you to scale \n   with your system.\n - **Centrally managed** - Allowing you to have centrally managed deployments, and rules. Changing rules doesn't\n   even require a new deployment. The rules are live reloaded. That means with 0 downtime you can add/modify \n   rules on the fly. \n\n## Usage\n\nMake sure you have `rules.hjson` in your current working directory when launching `rues`. Given following example\nrules:\n\n```hjson\n{\n  example_one: \"value == `2`\"\n  example_two: \"a.b\"\n}\n```\n\nEach rule is exposed under `/eval/{rule_name}` as `POST` endpoint, which in turn can be posted payload to evaluate\nthe expression. Simple use `curl` to test:\n\n```\n\u003e curl -X POST http://localhost:8080/eval/example_one -H 'Content-Type: application/json' -d '{\"value\": 2}'\n{\"Success\":{\"expression\":\"value == `2`\",\"name\":\"example_one\",\"is_truthy\":true,\"value\":true}}\n\u003e curl -X POST http://localhost:8080/eval/example_two -H 'Content-Type: application/json' -d '{\"a\": {\"b\": \"Hello\"}}'\n{\"Success\":{\"expression\":\"a.b\",\"name\":\"example_two\",\"is_truthy\":true,\"value\":\"Hello\"}}\n```\n\nResponse object contains `Success` if evaluation was successful. e.g.\n```json\n{\n   \"Success\": {\n      \"name\": \"filter_active\",\n      \"expression\": \"[?isActive] | length(@)\",\n      \"is_truthy\": true,\n      \"value\": 2\n   }\n}\n```\n\nResponse will have an `Error` if there was an error in expression or there was some violation while evaluating the \nexpression (in which case `reason` will contain a reason):\n\n```json\n{\n   \"Error\": {\n      \"name\": \"filter_registered\",\n      \"expression\": \"[?matched('^201\\\\d', registered)] | length(@)\",\n      \"reason\": \"Runtime error: Call to undefined function matched (line 0, column 9)\\n[?matched('^201\\\\d', registered)] | length(@)\\n         ^\\n\"\n   }\n}\n```\n\nResponse will have a `NotFound` if the specified rule is not found:\n\n```json\n{\n   \"NotFound\": {\n      \"name\": \"filter_register\"\n   }\n}\n```\n\n### Batch Rules API \n\nMany times you need evaluate a set of rules against a payload. RuES supports evaluating a context against multiple \nrules using batch API. Given the rules file:\n\n```hjson\n{\n  example_one: \"c == `2`\"\n  example_two: \"a.b\"\n}\n```\n\nOne can invoke batch api by simply invoking `/eval` with `POST` data of:\n```json\n{\n   \"context\": {\n      \"c\": 3,\n      \"a\": {\n         \"b\": true\n      } \n   },\n   \"rules\": [\"example_one\", \"example_two\", \"example_three\"]\n}\n```\n\nThe rules will be evaluated in sequence of order they were passed in, and server will return an array response:\n\n```json\n[\n   {\"Success\":{\"expression\":\"c == `2`\",\"name\":\"example_one\",\"is_truthy\":false,\"value\":false}},\n   {\"Success\":{\"expression\":\"a.b\",\"name\":\"example_two\",\"is_truthy\":true,\"value\":true}},\n   {\"NotFound\":{\"name\":\"example_three\"}}\n]\n```\n\n## Additional functions\n\nIn addition to [built-in functions](https://jmespath.org/proposals/functions.html#built-in-functions) of JMES, there \nadditional are following additional functions:\n\n - ✅ `string[] match(expref string $regex, string $element)` - Returns an array of all groups of regex matching \n   or a `null` if there is no match. Regex specs can be found [here](https://github.com/rust-lang/regex). \n   Regexes are compiled and cached in LRU order. The given Regex has to be an expression with string \n   literal e.g. `\u0026'\\d+'` this is required so that regexes are always string literal and never \n   variables eliminating any possibility of regex injection via variables, preventing any\n   exploits or accidental explosion of regex patterns. Examples:\n   ```jmes\n   [?match(\u0026'^[a-z0-9_-]{3,16}$', username)]\n   [?match(\u0026'^[a-z0-9_-]{3,16}$', 'user_123')]\n   [?match(\u0026'([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))', date)]\n   ```\n - ✅ `bool valid_email(string $element)` - Returns `true` or `false` based on email format. In \n   addition to formatting it also excludes temporary email addresses. Examples:\n   ```jmes\n   [?valid_email('user123@gmail.com')]\n   [?!valid_email('janette@guerrillamailblock.com')]\n   [?valid_email(contact.email)]\n   ```\n - ✅ `number now()` - Returns current time unix timestamp as 64-bit float with number of seconds since `1970-01-01`\n   the fractional part of timestamp contains upto microseconds of the timestamp. Examples:\n   ```jmes\n   now()\n   \u003e 1641257813.803243\n   ```\n - ✅ `number duration(string $element)` - Parses a duration string with same specs as \n   [systemd.time](https://www.freedesktop.org/software/systemd/man/systemd.time.html#Parsing%20Time%20Spans) and returns\n   a unix timestamp just like `now`. This will allow anyone to perform addition/subtraction operations on timestamps,\n   Examples:\n   ```jmes\n   duration('1min100ms')\n   \u003e 60.1\n   duration('1h')\n   \u003e 360.0\n   ```\n - 🚧 `number parse_datetime(string $element[, string $format = 'rfc3339'])` (To be implemented yet) - Converts \n   datetime in given format to a timestamp. The timestamp then in turn can be used to \n   do comparisons or reformatting. \n - 🚧 `string to_datetime(number $element[, string $format = 'rfc3339'])` (To be implemented yet) - Converts\n   timestamp to a given string format.\n - 🚧 `bool in_geo_fence(number[] $center, number $radius, number[] $element)` (To be implemented yet) - Returns `true`\n   or `false` if the `$element` lies within the `$radius` of `$center`.\n - 🚧 `number[][] filter_in_geo_fence(number[] $center, number $radius, number[][] $elements) ` (To be implemented yet) - \n   Returns all elements that lie within geo fence of given radius and center.\n - 🚧 `bool match_glob(string $pattern, string $element)` (To be implemented yet) - Returns `true` or `false` \n   if the `$element` is a glob match of the `$pattern`.\n\n## Configuration variables\n\n - `CONFIG_PATH` - path to rules file, file can be `.json`, `.yaml`, or `.hjson`. Default: `rules.hjson`\n - `BIND_ADDRESS` - service address to bind to. Default: `0.0.0.0:8080`\n\n## Benchmarks\n\nMy brief stress testing shows with a single CPU core (single worker), 3 rules, and payload size of 1.6 KB. Server was \neasily able to handle 10K RPS (even with sustained load) under **19 MB of RSS** memory footprint, and a p99 of 4ms.\n\n```\n$ cat vegeta_attack.txt | vegeta attack -duration=10s -rate=10000 | vegeta report \nRequests      [total, rate, throughput]         100000, 10000.20, 9999.80\nDuration      [total, attack, wait]             10s, 10s, 394.927µs\nLatencies     [min, mean, 50, 90, 95, 99, max]  107.266µs, 811.954µs, 285.329µs, 2.128ms, 2.654ms, 4.517ms, 12.373ms\nBytes In      [total, mean]                     9566673, 95.67\nBytes Out     [total, mean]                     166000000, 1660.00\nSuccess       [ratio]                           100.00%\nStatus Codes  [code:count]                      200:100000  \nError Set:\n```\n\nWith two CPU cores (two workers), the results were even better:\n```\n$ cat vegeta_attack.txt | vegeta attack -duration=10s -rate=10000 | vegeta report\nRequests      [total, rate, throughput]         100000, 10000.30, 10000.08\nDuration      [total, attack, wait]             10s, 10s, 217.653µs\nLatencies     [min, mean, 50, 90, 95, 99, max]  111.479µs, 270.125µs, 219.274µs, 413.215µs, 564.181µs, 1.021ms, 8.184ms\nBytes In      [total, mean]                     9566673, 95.67\nBytes Out     [total, mean]                     166000000, 1660.00\nSuccess       [ratio]                           100.00%\nStatus Codes  [code:count]                      200:100000  \nError Set:\n```\n\nAll the rules, and data has been shipped under `stress_test`. Feel free to share your results, and I will be more \nthan happy to include your results.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpert%2Frues","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxpert%2Frues","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpert%2Frues/lists"}