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.0.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.8.0 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.hh>

int main()
{
    rego::Interpreter rego;
    rego.add_module("objects", 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"])");
    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
    }
})");
    rego.add_data_json(R"({
    "three": {
        "bar": "Baz",
        "baz": 15,
        "be": true,
        "bop": 4.23
    }
})");
    rego.set_input_json(R"({
    "a": 10,
    "b": "20",
    "c": 30.0,
    "d": true
})");
    std::cout << rego.query("[data.one, input.b, data.objects.sites[1]] = x") << std::endl;
}
example.cc
C

You can also compile rego-cpp directly into your C program. The C API provides most of the functionality of the full C++ API while providing a stable interface which is amenable to FFI. 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>
#include <stdlib.h>
#include <stdio.h>

int main()
{
    regoEnum err;
    int rc = EXIT_SUCCESS;
    regoOutput* output = NULL;
    regoInterpreter* rego = regoNew();

    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;
    }

    printf("%s\n", regoOutputString(output));
    goto exit;
    
error:
  printf("%s\n", regoGetError(rego));
  rc = EXIT_FAILURE;

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

  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{
    {"one", new Dictionary{
        {"bar", "Foo"},
        {"baz", 5},
        {"be", true},
        {"bop", 23.4}}},
    {"two", new Dictionary{
        {"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
        {
            {"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
                        from regopy import Interpreter

rego = Interpreter()
with open("examples/objects.rego") as f:
    rego.add_module("objects", f.read())

rego.add_data({
    "one": {
        "bar": "Foo",
        "baz": 5,
        "be": True,
        "bop": 23.4
    },
    "two": {
        "bar": "Bar",
        "baz": 12.3,
        "be": False,
        "bop": 42
    }
})

rego.add_data({
    "three": {
        "bar": "Baz",
        "baz": 15,
        "be": True,
        "bop": 4.23
    }
})

rego.set_input({
    "a": 10,
    "b": "20",
    "c": 30.0,
    "d": True
})

print(rego.query("[data.one, input.b, data.objects.sites[1]] = x"))
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 = "0.4.5"
Cargo.toml
                        use clap::Parser;
use regorust::Interpreter;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
    #[arg(short, long)]
    data: Vec<std::path::PathBuf>,

    #[arg(short, long)]
    input: Option<std::path::PathBuf>,

    query: String,
}

fn main() {
    let args = Args::parse();
    let rego = Interpreter::new();
    if let Some(input) = args.input {
        rego.set_input_json_file(input.as_path())
            .expect("Failed to read input file");
    }

    for data in args.data {
        if data.extension().unwrap() == "rego" {
            rego.add_module_file(data.as_path())
                .expect("Failed to load module file");
        } else {
            rego.add_data_json_file(data.as_path())
                .expect("Failed to load data file");
        }
    }

    println!(
        "{}",
        rego.query(args.query.as_str())
            .expect("Failed to evaluate query")
    );
}
main.rs