Skip to content

Commit

Permalink
WASM binding (#114)
Browse files Browse the repository at this point in the history
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
  • Loading branch information
anakrish authored Jan 28, 2024
1 parent 0af9784 commit 055bdd2
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 0 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/publish-wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: publish-wasm

permissions:
pull-requests: write
contents: write

on: workflow_dispatch

jobs:
publish-wasm:
name: publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Build
run: wasm-pack build --target nodejs -r
- name: Publish
run: # TODO
8 changes: 8 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ jobs:
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing)
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

- name: Run wasm binding tests
run: |
cd bindings/wasm
wasm-pack test --node -r
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
workspace = { members = ["bindings/wasm"] }
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ $ cargo build -r --example regorus --features "yaml" --no-default-features; stri
Regorus passes the [OPA v0.60.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
builtins. See [OPA Conformance](#opa-conformance) below.

## Bindings

Regorus can be used from a variety of languages:

- Javascript (nodejs): Via npm package `regorus-wasm`. This package is Regorus compiled into WASM.

## Getting Started

[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that
Expand Down
19 changes: 19 additions & 0 deletions bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "regorus-wasm"
version = "0.1.0"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/wasm"
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
keywords = ["interpreter", "opa", "policy-as-code", "rego"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]

[dependencies]
regorus = { path = "../.." }
serde_json = "1.0.111"
wasm-bindgen = "0.2.90"

[dev-dependencies]
wasm-bindgen-test = "0.3.40"
28 changes: 28 additions & 0 deletions bindings/wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# regorus-wasm

**Regorus** is

- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
interpreter written in Rust.
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.

See [Repository](https://github.com/microsoft/regorus).

`regorus-wasm` is Regorus compiled into WASM.

## Usage

In nodejs,

``javascript

var regorus = require('regorus-wasm')

// Create an engine.
var engine = new regorus.Engine();

// Add Rego policy.
engine.add_policy()


```
138 changes: 138 additions & 0 deletions bindings/wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
/// WASM wrapper for [`regorus::Engine`]
pub struct Engine {
engine: regorus::Engine,
}

fn error_to_jsvalue<E: std::fmt::Display>(e: E) -> JsValue {
JsValue::from_str(&format!("{e}"))
}

impl Default for Engine {
fn default() -> Self {
Self::new()
}
}

impl Clone for Engine {
/// Clone a [`Engine`]
///
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
fn clone(&self) -> Self {
Self {
engine: self.engine.clone(),
}
}
}

#[wasm_bindgen]
impl Engine {
#[wasm_bindgen(constructor)]
/// Construct a new Engine
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
pub fn new() -> Self {
Self {
engine: regorus::Engine::new(),
}
}

/// Add a policy
///
/// The policy is parsed into AST.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: Rego policy.
pub fn add_policy(&mut self, path: String, rego: String) -> Result<(), JsValue> {
self.engine.add_policy(path, rego).map_err(error_to_jsvalue)
}

/// Add policy data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
/// * `data`: JSON encoded value to be used as policy data.
pub fn add_data_json(&mut self, data: String) -> Result<(), JsValue> {
let data = regorus::Value::from_json_str(&data).map_err(error_to_jsvalue)?;
self.engine.add_data(data).map_err(error_to_jsvalue)
}

/// Set input.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input
/// * `input`: JSON encoded value to be used as input to query.
pub fn set_input_json(&mut self, input: String) -> Result<(), JsValue> {
let input = regorus::Value::from_json_str(&input).map_err(error_to_jsvalue)?;
self.engine.set_input(input);
Ok(())
}

/// Evaluate query.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query
/// * `query`: Rego expression to be evaluate.
pub fn eval_query(&mut self, query: String) -> Result<String, JsValue> {
let results = self
.engine
.eval_query(query, false)
.map_err(error_to_jsvalue)?;
serde_json::to_string_pretty(&results).map_err(error_to_jsvalue)
}
}

#[cfg(test)]
mod tests {
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::wasm_bindgen_test;

#[wasm_bindgen_test]
pub fn basic() -> Result<(), JsValue> {
let mut engine = crate::Engine::new();

// Exercise all APIs.
engine.add_data_json(
r#"
{
"foo" : "bar"
}
"#
.to_string(),
)?;

engine.set_input_json(
r#"
{
"message" : "Hello"
}
"#
.to_string(),
)?;

engine.add_policy(
"hello.rego".to_string(),
r#"
package test
message = input.message"#
.to_string(),
)?;

let results = engine.eval_query("data".to_string())?;
let r = regorus::Value::from_json_str(&results).map_err(crate::error_to_jsvalue)?;

let v = &r["result"][0]["expressions"][0]["value"];

// Ensure that input and policy were evaluated.
assert_eq!(v["test"]["message"], regorus::Value::from("Hello"));

// Test that data was set.
assert_eq!(v["foo"], regorus::Value::from("bar"));

Ok(())
}
}

0 comments on commit 055bdd2

Please sign in to comment.