Skip to main content
Glama
lib.rs7.65 kB
use std::result; use si_data_spicedb::{ Relationship, Relationships, SpiceDBObject, SpiceDbClient, SpiceDbError, ZedToken, }; use si_events::{ UserPk, WorkspacePk, }; use thiserror::Error; #[remain::sorted] #[derive(Error, Debug)] pub enum Error { #[error("Builder must contain object, permission, and subject.")] PermissionBuilder, #[error( "All of the following fields are required for this call: {:?}", required_fields )] RelationBuilder { required_fields: Vec<String> }, #[error("spicedb client error: {0}")] SpiceDb(#[from] Box<SpiceDbError>), } impl From<SpiceDbError> for Error { fn from(value: SpiceDbError) -> Self { Box::new(value).into() } } type Result<T> = result::Result<T, Error>; #[derive(Clone, Copy, strum::Display, Debug)] #[strum(serialize_all = "snake_case")] pub enum ObjectType { User, Workspace, } #[derive(Clone, Copy, strum::Display)] #[strum(serialize_all = "snake_case")] pub enum Permission { Approve, Manage, } #[derive(Clone, Copy, strum::Display, Debug)] #[strum(serialize_all = "snake_case")] pub enum Relation { Approver, Owner, } /// RelationBuilder allows defining a relationship in SpiceDb. /// Relationships work by saying object -> relation -> subject, /// so `workspace 123` has `approver` of `user 456`. /// The object, relation, and subject must be set. /// /// # Examples /// ```no_run /// RelationBuilder::new() /// .object(ObjectType::Workspace, workspace_id.clone()) /// .relation(Relation::Approver) /// .subject(ObjectType::User, user_id.clone()) /// .create(client)) /// .await?; /// ``` pub struct RelationBuilder { object: Option<SpiceDBObject>, relation: Option<Relation>, subject: Option<SpiceDBObject>, zed_token: Option<ZedToken>, } impl RelationBuilder { pub fn new() -> Self { Self { object: None, relation: None, subject: None, zed_token: None, } } pub fn object(mut self, object_type: ObjectType, id: impl ToString) -> Self { self.object = Some(SpiceDBObject::new(object_type, id)); self } pub fn workspace_object(self, id: WorkspacePk) -> Self { self.object(ObjectType::Workspace, id) } pub fn relation(mut self, relation: Relation) -> Self { self.relation = Some(relation); self } pub fn subject(mut self, object_type: ObjectType, id: impl ToString) -> Self { self.subject = Some(SpiceDBObject::new(object_type, id)); self } pub fn user_subject(self, id: UserPk) -> Self { self.subject(ObjectType::User, id) } pub fn zed_token(mut self, token: ZedToken) -> Self { self.zed_token = Some(token.clone()); self } /// Creates a new relationship in SpiceDb pub async fn create(&self, client: &mut SpiceDbClient) -> Result<Option<ZedToken>> { match self.check() { Ok(relationship) => client .create_relationships(vec![relationship]) .await .map_err(Into::into), Err(err) => Err(err), } } /// Deletes an existing relationship in SpiceDb pub async fn delete(&self, client: &mut SpiceDbClient) -> Result<Option<ZedToken>> { match self.check() { Ok(relationship) => client .delete_relationships(vec![relationship]) .await .map_err(Into::into), Err(err) => Err(err), } } /// Reads existing relations in SpiceDb for a given object and relation pub async fn read(&self, client: &mut SpiceDbClient) -> Result<Relationships> { match (self.object.clone(), self.relation) { (Some(object), Some(relation)) => client .read_relationship(Relationship::new( object, relation, SpiceDBObject::empty(), self.zed_token.clone(), )) .await .map_err(Into::into), _ => Err(Error::RelationBuilder { required_fields: vec!["object".to_string(), "relation".to_string()], }), } } fn check(&self) -> Result<Relationship> { match (self.object.clone(), self.relation, self.subject.clone()) { (Some(object), Some(relation), Some(subject)) => Ok(Relationship::new( object, relation, subject, self.zed_token.clone(), )), _ => Err(Error::RelationBuilder { required_fields: vec![ "object".to_string(), "relation".to_string(), "subject".to_string(), ], }), } } } impl Default for RelationBuilder { fn default() -> Self { Self::new() } } /// PermissionBuilder allows checking a permission in SpiceDb. /// Permissions checks work by saying object -> permission -> subject, /// so `workspace 123` allows `approve` for `user 456`. /// The object, permission, and subject must be set. /// /// # Examples /// ```no_run /// if (PermissionBuilder::new() /// .object(ObjectType::Workspace, workspace_id.clone()) /// .permission(Permission::Approve) /// .subject(ObjectType::User, user_id.clone()) /// .has_permission(client) /// .await?) { do_thing() } /// ``` pub struct PermissionBuilder { object: Option<SpiceDBObject>, permission: Option<Permission>, subject: Option<SpiceDBObject>, zed_token: Option<ZedToken>, } impl PermissionBuilder { pub fn new() -> Self { Self { object: None, permission: None, subject: None, zed_token: None, } } pub fn object(mut self, object_type: ObjectType, id: impl ToString) -> Self { self.object = Some(SpiceDBObject::new(object_type, id)); self } pub fn workspace_object(self, id: WorkspacePk) -> Self { self.object(ObjectType::Workspace, id) } pub fn permission(mut self, permission: Permission) -> Self { self.permission = Some(permission); self } pub fn subject(mut self, object_type: ObjectType, id: impl ToString) -> Self { self.subject = Some(SpiceDBObject::new(object_type, id)); self } pub fn user_subject(self, id: UserPk) -> Self { self.subject(ObjectType::User, id) } pub fn zed_token(mut self, token: ZedToken) -> Self { self.zed_token = Some(token.clone()); self } /// Checks if the given subject has the given permission in the given object pub async fn has_permission(&self, client: &mut SpiceDbClient) -> Result<bool> { match self.check_has_permission() { Ok(perms) => client.check_permissions(perms).await.map_err(Into::into), Err(err) => Err(err), } } fn check_has_permission(&self) -> Result<si_data_spicedb::Permission> { match (self.object.clone(), self.permission, self.subject.clone()) { (Some(object), Some(permission), Some(subject)) => { Ok(si_data_spicedb::Permission::new( object, permission, subject, self.zed_token.clone(), )) } _ => Err(Error::PermissionBuilder), } } } impl Default for PermissionBuilder { fn default() -> Self { Self::new() } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server