spicedb-rust 0.3.4

A client for spicedb
Documentation
use pretty_assertions::assert_eq;
use spicedb_rust::spicedb::{
    relationship_update, subject_reference_raw, wildcard_relationship_update, SubjectReference,
};
use spicedb_rust::IntoStaticStr;
use spicedb_rust::{Actor, Entity, NoRelations, RelationshipOperation, Resource, SpiceDBClient};
use uuid::Uuid;

struct User(Uuid);

impl User {
    pub fn new(id: Uuid) -> Self {
        Self(id)
    }
}

impl Entity for User {
    type Relations = NoRelations;
    type Id = Uuid;

    fn object_type() -> &'static str {
        "user"
    }
}

impl Actor for User {
    fn to_subject(&self) -> SubjectReference {
        subject_reference_raw(self.0, User::object_type(), None::<String>)
    }
}

struct Document;

#[derive(IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum DocumentPermission {
    Read,
    Write,
}

impl Entity for Document {
    type Relations = DocumentRelation;
    type Id = String;

    fn object_type() -> &'static str {
        "document"
    }
}

#[derive(IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum DocumentRelation {
    Reader,
    Writer,
}

impl Resource for Document {
    type Permissions = DocumentPermission;
}

#[tokio::test]
async fn example() {
    let client = SpiceDBClient::new_isolated("http://localhost:50051")
        .await
        .unwrap();
    let schema = include_str!("schema.zed");
    client.write_schema(schema.to_owned()).await.unwrap();

    let user_id = Uuid::now_v7();
    let relationships = [
        relationship_update::<User, Document>(
            RelationshipOperation::Touch,
            user_id,
            None,
            "homework",
            DocumentRelation::Writer,
        ),
        wildcard_relationship_update::<User, Document>(
            RelationshipOperation::Touch,
            "manga",
            DocumentRelation::Reader,
        ),
    ];
    let token = client
        .create_relationships(relationships, [])
        .await
        .unwrap();

    let actor = User::new(user_id);
    let authorized = client
        .check_permission_at::<Document>(
            &actor,
            "homework",
            DocumentPermission::Write,
            token.clone(),
        )
        .await
        .unwrap();
    assert!(
        authorized,
        "User should be authorized to write the document"
    );

    let random_user_actor = User::new(Uuid::now_v7());
    let authorized = client
        .check_permission_at::<Document>(
            &random_user_actor,
            "manga",
            DocumentPermission::Read,
            token.clone(),
        )
        .await
        .unwrap();
    assert!(
        authorized,
        "Random user should be authorized to read `manga` due to wildcard"
    );

    let mut resource_ids = client
        .lookup_resources_at::<Document>(&actor, DocumentPermission::Read, token.clone())
        .await
        .unwrap();
    let mut expected = vec!["homework", "manga"];
    resource_ids.sort();
    expected.sort();
    assert_eq!(
        resource_ids, expected,
        "Homework and Manga should both appear in documents User can read"
    );

    let subject_ids = client
        .lookup_subjects_at::<User, Document>("homework", DocumentPermission::Write, token.clone())
        .await
        .unwrap();
    assert_eq!(subject_ids, vec![user_id]);

    let new_token = client
        .delete_relationships::<Document>(None, None, None)
        .await
        .unwrap();

    let authorized = client
        .check_permission_at::<Document>(
            &random_user_actor,
            "manga",
            DocumentPermission::Read,
            new_token,
        )
        .await
        .unwrap();
    assert!(
        !authorized,
        "Nobody should be authorized to read `manga` due to nuking all relationships"
    );
}