oso 0.26.4

oso is an open source policy engine for authorization that’s embedded in your application
Documentation
use oso::{ClassBuilder, Oso, PolarClass};
use polar_core::error::{ErrorKind, PolarError, RuntimeError};

macro_rules! res {
    ($res:expr) => {
        $res.unwrap().next().unwrap().unwrap();
    };
    (@not $res:expr) => {
        assert!($res.unwrap().next().is_none());
    };
}

#[derive(Clone)]
struct A {
    x: String,
}

impl PolarClass for A {}

impl A {
    pub fn new(x: String) -> Self {
        Self { x }
    }

    pub fn foo(&self) -> i32 {
        -1
    }
}

// pub trait A {}

// TODO
// struct D;

// impl A for D {

// }

// oso.register_class(A)

pub mod b {
    #[derive(Clone, Default)]
    pub struct C {
        pub y: String,
    }

    impl C {
        pub fn new(y: String) -> Self {
            Self { y }
        }

        pub fn foo(&self) -> i32 {
            -1
        }
    }

    impl oso::PolarClass for C {}
}

#[test]
fn test() {
    let mut oso = Oso::new();
    tracing_subscriber::fmt::init();

    oso.register_class(
        ClassBuilder::with_constructor(A::new)
            .name("A")
            .add_attribute_getter("x", |a_self: &A| a_self.x.clone())
            .add_method("foo", A::foo)
            .build(),
    )
    .unwrap();

    oso.register_class(
        ClassBuilder::with_constructor(b::C::new)
            .name("C")
            .add_attribute_getter("y", |c: &b::C| c.y.clone())
            .add_method("foo", b::C::foo)
            .build(),
    )
    .unwrap();

    assert_eq!(
        oso.load_str("missingSemicolon()").unwrap_err().to_string(),
        "hit the end of the file unexpectedly. Did you forget a semi-colon at line 1, column 19:\n\t001: missingSemicolon()\n\t                       ^\n"
    );

    // Test that a built in string method can be called.
    oso.load_str(r#"?= x = "hello world!" and x.ends_with("world!");"#)
        .unwrap();

    oso.clear_rules().unwrap();

    let polar_file = std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/../../../test/test.polar";
    println!("Loading: {}", polar_file);
    oso.load_files(vec![&polar_file]).unwrap();

    assert!(oso.is_allowed("a", "b", "c").unwrap());

    // let d = D("")
    // let args: Vec<&ToPolar> = vec![]
    // assert!(oso.query_rule("specializers", ))
    // assert list(oso.query_rule("specializers", D("hello"), B.C("hello")))
    res!(oso.query_rule("floatLists", ()));
    res!(oso.query_rule("intDicts", ()));
    res!(oso.query_rule("comparisons", ()));
    res!(oso.query_rule("testForall", ()));
    res!(oso.query_rule("testRest", ()));
    let a = A::new("hello".to_string());
    res!(oso.query_rule("testMatches", (a.clone(),)));

    let c = b::C::new("hello".to_string());
    res!(oso.query_rule("testMethodCalls", (a, c)));
    res!(oso.query_rule("testOr", ()));

    // Test that cut doesn't return anything.
    res!(@not oso.query_rule("testCut", ()));

    // Test that a constant can be called.
    // oso.register_constant(math, "MyMath");
    // oso.load_str("?= MyMath.factorial(5) == 120;").unwrap();

    // Test built-in type specializers.
    res!(oso.query(r#"builtinSpecializers(true, "Boolean")"#));
    res!(@not oso.query(r#"builtinSpecializers(false, "Boolean")"#));
    res!(oso.query(r#"builtinSpecializers(2, "Integer")"#));
    res!(oso.query(r#"builtinSpecializers(1, "Integer")"#));
    res!(@not oso.query(r#"builtinSpecializers(0, "Integer")"#));
    res!(@not oso.query(r#"builtinSpecializers(-1, "Integer")"#));
    res!(oso.query(r#"builtinSpecializers(1.0, "Float")"#));
    res!(@not oso.query(r#"builtinSpecializers(0.0, "Float")"#));
    res!(@not oso.query(r#"builtinSpecializers(-1.0, "Float")"#));
    res!(oso.query(r#"builtinSpecializers(["foo", "bar", "baz"], "List")"#));
    res!(@not oso.query(r#"builtinSpecializers(["bar", "foo", "baz"], "List")"#));
    res!(oso.query(r#"builtinSpecializers({foo: "foo"}, "Dictionary")"#));
    res!(@not oso.query(r#"builtinSpecializers({foo: "bar"}, "Dictionary")"#));
    res!(oso.query(r#"builtinSpecializers("foo", "String")"#));
    res!(@not oso.query(r#"builtinSpecializers("bar", "String")"#));

    // Rust ints do not have the denominator field
    // res!(oso.query(r#"builtinSpecializers(1, "IntegerWithFields")"#));
    res!(@not oso.query(r#"builtinSpecializers(2, "IntegerWithGarbageFields")"#));
    res!(@not oso.query(r#"builtinSpecializers({}, "DictionaryWithFields")"#));
    res!(@not oso.query(r#"builtinSpecializers({z: 1}, "DictionaryWithFields")"#));
    res!(oso.query(r#"builtinSpecializers({y: 1}, "DictionaryWithFields")"#));

    let res = oso.query("testUnhandledPartial()").unwrap().next().unwrap();
    assert!(
        matches!(
            res,
            Err(oso::OsoError::Polar(PolarError(ErrorKind::Runtime(
                RuntimeError::UnhandledPartial { .. }
            )))),
        ),
        "Expected unhandled partial error, got: {:#?}",
        res
    )
}