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
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.
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.
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
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
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
We provide a Rust crate called regorust which exposes the C API in idiomatic Rust. This example shows a CLI interpreter implemented in Rust.
[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