rego-cpp
rego-cpp

Perform Rego queries from multiple languages with rego-cpp

A Rego language interpreter in C++ with wrappers for C, Rust, and Python.

Currently v1.4.0·All releases

Open Policy Agent

What is Rego?

Rego is a language developed by Open Policy Agent (OPA) for use in defining policies in cloud systems.

We think Rego is a great language, and you can learn more about it here. However, it has some limitations. Primarily, the interpreter is only accessible via the command line or a web server. Further, the only option for using the language in-process is via an interface in Go.

The rego-cpp project provides the ability to integrate Rego natively into a wider range of languages. We currrently support C, C++, dotnet, Python, and Rust, and are largely compatible with v1.15.1 of the language. You can learn more about our implementation here.

Take a look at our examples

A great way to get started is by looking at one of our code examples.

c++

You can compile rego-cpp directly into your C++ program, which gives you full access to our API. We recommend using CMake FetchContent to add rego-cpp to your project.

Learn more about the C++ API

                        FetchContent_Declare(
  regocpp
  GIT_REPOSITORY https://github.com/microsoft/rego-cpp.git
  GIT_TAG        main
)
       
FetchContent_MakeAvailable(regocpp)

# ...

target_link_libraries(myproject PRIVATE regocpp::rego)
CMakeLists.txt
                        #include <rego/rego_c.h>

int main(void)
{
  regoEnum err;
  int rc = EXIT_SUCCESS;
  regoOutput* output = NULL;
  regoNode* node = NULL;
  regoBundle* bundle = NULL;
  regoInput* input = NULL;
  regoInterpreter* rego = regoNew();
  regoSize size = 0;
  char* buf = NULL;
  const char* bundle_dir = "example_bundle";
  const char* bundle_path = "example.rbb";

  err = regoAddModuleFile(rego, "examples/objects.rego");
  if (err != REGO_OK)
  {
    goto error;
  }

  err = regoAddDataJSONFile(rego, "examples/data0.json");
  if (err != REGO_OK)
  {
    goto error;
  }

  err = regoAddDataJSONFile(rego, "examples/data1.json");
  if (err != REGO_OK)
  {
    goto error;
  }

  err = regoSetInputJSONFile(rego, "examples/input0.json");
  if (err != REGO_OK)
  {
    goto error;
  }

  output = regoQuery(rego, "[data.one, input.b, data.objects.sites[1]] = x");
  if (output == NULL)
  {
    goto error;
  }

  err = print_output("Query", output);
  if (err != REGO_OK)
  {
    goto error;
  }

  node = regoOutputBinding(output, "x");
  if (node == NULL)
  {
    goto error;
  }

  size = regoNodeJSONSize(node);
  if (size == 0)
  {
    goto error;
  }

  buf = (char*)malloc(size);
  err = regoNodeJSON(node, buf, size);
  if (err != REGO_OK)
  {
    goto error;
  }

  printf("x = %s\n", buf);
  free(buf);
  buf = NULL;

  node = regoNodeGet(node, 1); // index
  if (node == NULL)
  {
    goto error;
  }

  size = regoNodeValueSize(node);
  if (size == 0)
  {
    goto error;
  }

  buf = (char*)malloc(size);
  err = regoNodeValue(node, buf, size);
  if (err != REGO_OK)
  {
    goto error;
  }

  printf("x[1] = `%s`\n", buf);
  free(buf);
  buf = NULL;

  regoFreeOutput(output);
  output = NULL;

  err = regoSetQuery(rego, "[data.one, input.b, data.objects.sites[1]] = x");

  if (err != REGO_OK)
  {
    goto error;
  }

  err = regoAddEntrypoint(rego, "objects/sites");

  if (err != REGO_OK)
  {
    goto error;
  }

  bundle = regoBuild(rego);

  if (!regoBundleOk(bundle))
  {
    goto error;
  }

  err = regoBundleSave(rego, bundle_dir, bundle);

  if (err != REGO_OK)
  {
    goto error;
  }

  regoFreeBundle(bundle);
  bundle = NULL;

  bundle = regoBundleLoad(rego, bundle_dir);

  if (bundle == NULL || !regoBundleOk(bundle))
  {
    goto error;
  }

  err = regoBundleSaveBinary(rego, bundle_path, bundle);

  if (err != REGO_OK)
  {
    goto error;
  }

  regoFreeBundle(bundle);
  bundle = NULL;

  bundle = regoBundleLoadBinary(rego, bundle_path);

  if (bundle == NULL || !regoBundleOk(bundle))
  {
    goto error;
  }

  input = regoNewInput();
  regoInputString(input, "a");
  regoInputInt(input, 10);
  regoInputObjectItem(input);
  regoInputString(input, "b");
  regoInputString(input, "20");
  regoInputObjectItem(input);
  regoInputString(input, "c");
  regoInputFloat(input, 30.0);
  regoInputObjectItem(input);
  regoInputString(input, "d");
  regoInputBoolean(input, 1);
  regoInputObjectItem(input);
  regoInputObject(input, 4);
  err = regoInputValidate(input);

  if (err != REGO_OK)
  {
    goto error;
  }

  err = regoSetInput(rego, input);

  if (err != REGO_OK)
  {
    goto error;
  }

  output = regoBundleQuery(rego, bundle);
  if (output == NULL)
  {
    goto error;
  }

  err = print_output("Bundle Query", output);
  if (err != REGO_OK)
  {
    goto error;
  }

  regoFreeOutput(output);

  output = regoBundleQueryEntrypoint(rego, bundle, "objects/sites");
  if (output == NULL)
  {
    goto error;
  }

  err = print_output("Bundle Query Endpoint", output);
  if (err != REGO_OK)
  {
    goto error;
  }

  goto exit;

error:
  print_error(rego);
  rc = EXIT_FAILURE;

exit:
  if (buf != NULL)
  {
    free(buf);
  }

  if (output != NULL)
  {
    regoFreeOutput(output);
  }

  if (input != NULL)
  {
    regoFreeInput(input);
  }

  if (bundle != NULL)
  {
    regoFreeBundle(bundle);
  }

  if (rego != NULL)
  {
    regoFree(rego);
  }

  return rc;
}
example.c
dotnet

We provide a Nuget package called Rego which exposes the C API to dotnet languages.

Learn more about the dotnet API

                        $ dotnet add Rego
                        using System.Collections;
using Rego;

//////////////////////
///// Query only /////
//////////////////////

Console.WriteLine("Query Only");

// You can run simple queries without any input or data.
Interpreter rego = new();
var output = rego.Query("x=5;y=x + (2 - 4 * 0.25) * -3 + 7.4;2 * 5");
Console.WriteLine(output);
// {"expressions":[true, true, 10], "bindings":{"x":5, "y":9.4}}

// You can access bound results using the Binding method
Console.WriteLine("x = {0}", output.Binding("x"));

// You can also access expressions by index
Console.WriteLine(output.Expressions()[2]);

Console.WriteLine();

////////////////////////
//// Input and Data ////
////////////////////////

Console.WriteLine("Input and Data");

// If you provide an object, it will be converted to JSON before
// being added to the state.
var data0 = new Dictionary<string, object>{
    {"one", new Dictionary<string, object>{
        {"bar", "Foo"},
        {"baz", 5},
        {"be", true},
        {"bop", 23.4}}},
    {"two", new Dictionary<string, object>{
        {"bar", "Bar"},
        {"baz", 12.3},
        {"be", false},
        {"bop", 42}}}};
rego.AddData(data0);

// You can also provide JSON directly.
rego.AddDataJson("""
    {
        "three": {
            "bar": "Baz",
            "baz": 15,
            "be": true,
            "bop": 4.23
        }
    }
    """);

var objectsSource = """
    package objects

    rect := {"width": 2, "height": 4}
    cube := {"width": 3, "height": 4, "depth": 5}
    a := 42
    b := false
    c := null
    d := {"a": a, "x": [b, c]}
    index := 1
    shapes := [rect, cube]
    names := ["prod", "smoke1", "dev"]
    sites := [{"name": "prod"}, {"name": names[index]}, {"name": "dev"}]
    e := {
        a: "foo",
        "three": c,
        names[2]: b,
        "four": d,
    }
    f := e["dev"]
    """;
rego.AddModule("objects.rego", objectsSource);

// inputs can be either JSON or Rego, and provided
// as objects (which will be converted to JSON) or as
// text.
rego.SetInputTerm("""
    {
        "a": 10,
        "b": "20",
        "c": 30.0,
        "d": true
    }
    """);

Console.WriteLine(rego.Query("[data.one, input.b, data.objects.sites[1]] = x"));
// {"bindings":{"x":[{"bar":"Foo", "baz":5, "be":true, "bop":23.4}, "20", {"name":"smoke1"}]}}

///////////////////
///// Bundles  ////
///////////////////

Console.WriteLine();
Console.WriteLine("Bundles");

// If you want to run the same set of queries against a policy with different
// inputs, you can create a bundle and use that to save the cost of compilation.

Interpreter rego_build = new();

rego_build.AddDataJson("""
{"a": 7,
"b": 13}
""");

rego_build.AddModule("example.rego", """
    package example

    foo := data.a * input.x + data.b * input.y
    bar := data.b * input.x + data.a * input.y
""");

// We can specify both a default query, and specific entry points into the policy
// that should be made available to use later.
var bundle = rego_build.Build("x=data.example.foo + data.example.bar", ["example/foo", "example/bar"]);

// we can now save the bundle to the disk
rego_build.SaveBundle("bundle", bundle);

// and load it again
Interpreter rego_run = new();
rego_run.LoadBundle("bundle");

// the most efficient way to provide input to a policy is by constructing it
// manually, without the need for parsing JSON or Rego.

var input = Input.Create(new Dictionary<string, int>
        {
            {"x", 104 },
            {"y", 119 }
        });

rego_run.SetInput(input);

// We can query the bundle, which will use the entrypoint of the default query
// provided at build
Console.WriteLine("query: {0}",rego_run.QueryBundle(bundle));
// Query: {"expressions":[true], "bindings":{"x":4460}}

// or we can query specific entrypoints
Console.WriteLine("example/foo: {0}", rego_run.QueryBundle(bundle, "example/foo"));
// example/foo: {"expressions":[2275]}
Program.cs
Python

We provide a Python wheel called regopy which exposes the C API in a pythonic way.

Learn more about the Python API

                        $ pip install regopy
                        """Python example code."""

from regopy import Input, Interpreter

##############################
#####    Query Only      #####
##############################

print("Query Only")

# You can run simple queries without any input or data.
rego = Interpreter()
output = rego.query("x=5;y=x + (2 - 4 * 0.25) * -3 + 7.4;2 * 5")
print(output)
# {"expressions":[true, true, 10], "bindings":{"x":5, "y":9.4}}

# You can access bound results using the binding method
print("x =", output.binding("x").json())

# You can also access expressions by index
print(output[0][2])

print()

##############################
####   Input and Data    #####
##############################

print("Input and Data")

# If you provide a dict, it will be converted to JSON before
# being added to the state.
rego.add_data({
    "one": {
        "bar": "Foo",
        "baz": 5,
        "be": True,
        "bop": 23.4
    },
    "two": {
        "bar": "Bar",
        "baz": 12.3,
        "be": False,
        "bop": 42
    }
})

# You can also provide JSON directly.
rego.add_data_json("""
{
    "three": {
        "bar": "Baz",
        "baz": 15,
        "be": true,
        "bop": 4.23
    }
}
""")

objects_source = """
package objects

rect := {"width": 2, "height": 4}
cube := {"width": 3, "height": 4, "depth": 5}
a := 42
b := false
c := null
d := {"a": a, "x": [b, c]}
index := 1
shapes := [rect, cube]
names := ["prod", "smoke1", "dev"]
sites := [{"name": "prod"}, {"name": names[index]}, {"name": "dev"}]
e := {
    a: "foo",
    "three": c,
    names[2]: b,
    "four": d,
}
f := e["dev"]
"""
rego.add_module("objects.rego", objects_source)

# Inputs can be either JSON or Rego, and provided
# as objects or as text.
rego.set_input_term("""
{
    "a": 10,
    "b": "20",
    "c": 30.0,
    "d": true
}
""")

print(rego.query("[data.one, input.b, data.objects.sites[1]] = x"))
# {"bindings":{"x":[{"bar":"Foo", "baz":5, "be":true, "bop":23.4}, "20", {"name":"smoke1"}]}}

##############################
#####      Bundles       #####
##############################

print()
print("Bundles")

# If you want to run the same set of queries against a policy with different
# inputs, you can create a bundle and use that to save the cost of compilation.

rego_build = Interpreter()

rego_build.add_data_json("""
{"a": 7,
"b": 13}
""")

rego_build.add_module("example.rego", """
package example

foo := data.a * input.x + data.b * input.y
bar := data.b * input.x + data.a * input.y
""")

# We can specify both a default query, and specific entry points into the policy
# that should be made available to use later.
bundle = rego_build.build(
    "x=data.example.foo + data.example.bar",
    ["example/foo", "example/bar"]
)

# We can now save the bundle to disk
rego_build.save_bundle("bundle", bundle)

# And load it again
rego_run = Interpreter()
rego_run.load_bundle("bundle")

# The most efficient way to provide input to a policy is by constructing it
# manually, without the need for parsing JSON or Rego.
rego_run.set_input(Input({"x": 104, "y": 119}))

# We can query the bundle, which will use the entrypoint of the default query
# provided at build
print("query:", rego_run.query_bundle(bundle))
# query: {"expressions":[true], "bindings":{"x":4460}}

# Or we can query specific entrypoints
print("example/foo:", rego_run.query_bundle_entrypoint(bundle, "example/foo"))
# example/foo: {"expressions":[2275]}
example.py
Rust

We provide a Rust crate called regorust which exposes the C API in idiomatic Rust. This example shows a CLI interpreter implemented in Rust.

Learn more about the Rust API

                        [dependencies]
regorust = "1.4.0"
Cargo.toml
                        use regorust::{BundleFormat, Input, Interpreter};
use std::path::Path;

fn main() {
    //////////////////////
    ///// Query Only /////
    //////////////////////

    println!("Query Only");

    // You can run simple queries without any input or data.
    let rego = Interpreter::new();
    let output = rego
        .query("x=5;y=x + (2 - 4 * 0.25) * -3 + 7.4;2 * 5")
        .expect("Failed to evaluate query");
    println!("{}", output);
    // {"expressions":[true, true, 10], "bindings":{"x":5, "y":9.4}}

    // You can access bound results using the binding method
    let x = output.binding("x").expect("x is not bound");
    println!("x = {}", x.json().unwrap());

    // You can also access expressions by index
    let exprs = output.expressions().expect("no expressions");
    println!("{}", exprs.index(2).unwrap().json().unwrap());

    println!();

    ////////////////////////
    //// Input and Data ////
    ////////////////////////

    println!("Input and Data");

    // You can provide JSON data directly.
    rego.add_data_json(
        r#"{
        "one": {
            "bar": "Foo",
            "baz": 5,
            "be": true,
            "bop": 23.4
        },
        "two": {
            "bar": "Bar",
            "baz": 12.3,
            "be": false,
            "bop": 42
        }
    }"#,
    )
    .expect("Failed to add data");

    rego.add_data_json(
        r#"{
        "three": {
            "bar": "Baz",
            "baz": 15,
            "be": true,
            "bop": 4.23
        }
    }"#,
    )
    .expect("Failed to add data");

    rego.add_module(
        "objects.rego",
        r#"package objects

rect := {"width": 2, "height": 4}
cube := {"width": 3, "height": 4, "depth": 5}
a := 42
b := false
c := null
d := {"a": a, "x": [b, c]}
index := 1
shapes := [rect, cube]
names := ["prod", "smoke1", "dev"]
sites := [{"name": "prod"}, {"name": names[index]}, {"name": "dev"}]
e := {
    a: "foo",
    "three": c,
    names[2]: b,
    "four": d,
}
f := e["dev"]
"#,
    )
    .expect("Failed to add module");

    // Inputs can be either JSON or Rego, and provided as text.
    rego.set_input_json(
        r#"{
        "a": 10,
        "b": "20",
        "c": 30.0,
        "d": true
    }"#,
    )
    .expect("Failed to set input");

    println!(
        "{}",
        rego.query("[data.one, input.b, data.objects.sites[1]] = x")
            .expect("Failed to evaluate query")
    );
    // {"bindings":{"x":[{"bar":"Foo", "baz":5, "be":true, "bop":23.4}, "20", {"name":"smoke1"}]}}

    ///////////////////
    ///// Bundles  ////
    ///////////////////

    println!();
    println!("Bundles");

    // If you want to run the same set of queries against a policy with different
    // inputs, you can create a bundle and use that to save the cost of compilation.

    let rego_build = Interpreter::new();

    rego_build
        .add_data_json(
            r#"{"a": 7,
"b": 13}"#,
        )
        .expect("Failed to add data");

    rego_build
        .add_module(
            "example.rego",
            r#"package example

foo := data.a * input.x + data.b * input.y
bar := data.b * input.x + data.a * input.y
"#,
        )
        .expect("Failed to add module");

    // We can specify both a default query, and specific entry points into the policy
    // that should be made available to use later.
    let bundle = rego_build
        .build(
            &Some("x=data.example.foo + data.example.bar"),
            &["example/foo", "example/bar"],
        )
        .expect("Failed to build bundle");

    // We can now save the bundle to disk
    let bundle_path = Path::new("bundle");
    rego_build
        .save_bundle(bundle_path, &bundle, BundleFormat::JSON)
        .expect("Failed to save bundle");

    // And load it again
    let rego_run = Interpreter::new();
    rego_run
        .load_bundle(bundle_path, BundleFormat::JSON)
        .expect("Failed to load bundle");

    // The most efficient way to provide input to a policy is by constructing it
    // manually, without the need for parsing JSON or Rego.
    let input = Input::new()
        .str("x")
        .int(104)
        .objectitem()
        .str("y")
        .int(119)
        .objectitem()
        .object(2)
        .validate()
        .expect("Failed to create input");

    rego_run.set_input(&input).expect("Failed to set input");

    // We can query the bundle, which will use the entrypoint of the default query
    // provided at build
    println!(
        "query: {}",
        rego_run
            .query_bundle(&bundle)
            .expect("Failed to query bundle")
    );
    // query: {"expressions":[true], "bindings":{"x":4460}}

    // Or we can query specific entrypoints
    println!(
        "example/foo: {}",
        rego_run
            .query_bundle_entrypoint(&bundle, "example/foo")
            .expect("Failed to query bundle entrypoint")
    );
    // example/foo: {"expressions":[2275]}
}
main.rs