oso-cloud 0.5.4

Oso Cloud client
Documentation
use oso_cloud::{fact, Value};

macro_rules! assert_eq_lists {
    ($left:expr, $right:expr $(, $tt:tt)*) => {
        let mut left = $left;
        let mut right = $right;
        left.sort();
        right.sort();
        assert_eq!(left, right $($tt)*)
    };
}

struct User {
    id: u32,
}

impl From<User> for Value<'static> {
    fn from(user: User) -> Self {
        Value::new("User", user.id.to_string())
    }
}

impl<'a> From<&'a User> for Value<'static> {
    fn from(user: &'a User) -> Self {
        Value::new("User", user.id.to_string())
    }
}

#[derive(PartialEq, PartialOrd, Eq, Ord, Debug)]
struct Repo {
    id: u32,
}

impl From<Repo> for Value<'static> {
    fn from(repo: Repo) -> Self {
        Value::new("Repo", repo.id.to_string())
    }
}

impl<'a> From<&'a Repo> for Value<'a> {
    fn from(repo: &'a Repo) -> Self {
        Value::new("Repo", repo.id.to_string())
    }
}

// To run the tests you must first start the service locally in test mode.
// cargo run -- run --test-mode
// That loads in the test user so the tests can run.
#[tokio::test]
#[ignore] // requires service to be running
async fn base() {
    let _ = tracing_subscriber::fmt::try_init();

    let o = oso_cloud::Builder::from_env().build().unwrap();
    o.policy(
        r#"
        actor User {}

        resource Repo {
            roles = ["member"];
            permissions = ["read", "write"];
            relations = { parent: Repo };
            "read" if "member";
            "write" if "member";
        }
    "#,
    )
    .await
    .expect("Setting policy failed");

    let user = User { id: 1 };
    let repo = Repo { id: 1 };

    // delete using references to values that implement `From<T> for Value`
    o.delete(fact!("has_role", &user, "member", &repo))
        .await
        .expect("Error deleting");
    let allowed = o.authorize(&user, "read", &repo).await.unwrap();
    assert!(!allowed);

    // tell using literal syntax
    o.tell(fact!("has_role", User{"1"}, "member", Repo{"1"}))
        .await
        .expect("Error telling");

    let allowed = o.authorize(&user, "read", &repo).await.unwrap();
    assert!(allowed);

    let allowed_actions = o.actions(&user, &repo).await.unwrap();
    assert_eq_lists!(allowed_actions, vec!["read", "write"]);

    let allowed_resources = o
        .with_context(vec![fact!("has_role", &user, "member", Repo{"2"})])
        .list(&user, "write", "Repo")
        .await
        .unwrap();
    assert_eq_lists!(allowed_resources, vec!["1", "2"]);

    let mut resources = vec![Repo { id: 1 }, Repo { id: 2 }];
    o.authorize_resources(&user, "read", &mut resources).await.unwrap();
    let expected = vec![Repo { id: 1 }];
    assert_eq_lists!(resources, expected);

    let mut resources = vec![Repo { id: 2 }, Repo { id: 1 }];
    o.authorize_resources(&user, "read", &mut resources).await.unwrap();
    let expected = vec![Repo { id: 1 }];
    assert_eq_lists!(resources, expected);

    let facts = o
        .get(&fact!("has_role", User { "1" }, "member", Repo { "1" }))
        .await
        .unwrap();
    assert_eq_lists!(
        facts,
        vec![fact! {
            "has_role",
            User{"1"},
            "member",
            Repo{"1"}
        }]
    );

    o.delete(fact!("has_role", &user, "member", &repo))
        .await
        .expect("Error deleting");

    let facts = o.get(&fact!("has_role", &user, "member", &repo)).await.unwrap();
    assert_eq_lists!(facts, vec![]);

    // get a vec of facts ready
    let bulk_facts = vec![(1, 1)]
        .into_iter()
        .map(|(actor, resource)| fact!("has_role", User { actor.to_string() }, "member", Repo { resource.to_string() }))
        .collect::<Vec<_>>();

    o.bulk_tell(&bulk_facts).await.expect("Error telling");

    let facts = o.get(&fact!("has_role", &user, "member", &repo)).await.unwrap();
    assert_eq_lists!(
        facts,
        vec![fact! {
            "has_role",
            User{"1"},
            "member",
            Repo{"1"}
        }]
    );

    let facts = o
        .query(&fact!("has_permission", &user, Value::any(), &repo))
        .await
        .unwrap();
    assert_eq_lists!(
        facts,
        vec![
            fact! {
                "has_permission",
                User{"1"},
                "read",
                Repo{"1"}
            },
            fact! {
                "has_permission",
                User{"1"},
                "write",
                Repo{"1"}
            }
        ]
    );

    o.bulk_delete(&[fact!("has_role", &user, "member", &repo)])
        .await
        .expect("Error deleting");
    let facts = o.get(&fact!("has_role", &user, "member", &repo)).await.unwrap();
    assert_eq_lists!(facts, vec![]);

    o.with_context(vec![fact!("has_role", &user, "member", Repo{"2"})])
        .authorize(&user, "read", &Repo { id: 2 })
        .await
        .unwrap();
    // Test API error
    let api_error_result = o.tell(fact!("does_not_exist", User{"1"}, "taco")).await;
    let err = api_error_result.unwrap_err();
    let err_message = err.to_string();
    assert!(err_message.starts_with("Oso Server Error: "));

    let large_payload = vec!["a".repeat(1024 * 1024); 10].join(""); // 10MB payload
    let payload_error_result = o.tell(fact!("has_role", User{"1"}, large_payload)).await;
    let payload_error = payload_error_result.unwrap_err();
    assert!(payload_error
        .to_string()
        .starts_with("Input error: Request payload too large"));
}