Skip to main content

bitwarden_organizations/
organizations_client.rs

1//! [`OrganizationsClient`] and its associated extension trait.
2
3use std::sync::Arc;
4
5use bitwarden_core::{Client, FromClient, OrganizationId};
6use bitwarden_state::repository::{Repository, RepositoryError, RepositoryOption};
7
8use crate::ProfileOrganization;
9
10/// Client for reading the current user's organizations in sync data.
11#[derive(FromClient)]
12pub struct OrganizationsClient {
13    pub(crate) repository: Option<Arc<dyn Repository<ProfileOrganization>>>,
14}
15
16impl OrganizationsClient {
17    /// Get the organization with the given ID, or `None` if it does not exist or the user does not
18    /// have access.
19    pub async fn get_by_id(
20        &self,
21        organization_id: OrganizationId,
22    ) -> Result<Option<ProfileOrganization>, RepositoryError> {
23        self.repository.require()?.get(organization_id).await
24    }
25
26    /// Get all of the current user's organizations.
27    pub async fn get_all(&self) -> Result<Vec<ProfileOrganization>, RepositoryError> {
28        self.repository.require()?.list().await
29    }
30}
31
32/// Extension trait to add the organizations client to the main Bitwarden SDK client.
33pub trait OrganizationsClientExt {
34    /// Get the organizations client.
35    fn organizations(&self) -> OrganizationsClient;
36}
37
38impl OrganizationsClientExt for Client {
39    fn organizations(&self) -> OrganizationsClient {
40        OrganizationsClient::from_client(self)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use bitwarden_test::MemoryRepository;
47    use uuid::Uuid;
48
49    use super::*;
50    use crate::OrganizationDetails;
51
52    fn organization(id: OrganizationId, name: &str) -> ProfileOrganization {
53        ProfileOrganization {
54            details: OrganizationDetails {
55                id,
56                name: name.to_string(),
57                ..OrganizationDetails::default()
58            },
59            ..ProfileOrganization::default()
60        }
61    }
62
63    async fn client_with(organizations: &[ProfileOrganization]) -> OrganizationsClient {
64        let repository = Arc::new(MemoryRepository::<ProfileOrganization>::default());
65        for org in organizations {
66            repository.set(org.details.id, org.clone()).await.unwrap();
67        }
68        OrganizationsClient {
69            repository: Some(repository),
70        }
71    }
72
73    #[tokio::test]
74    async fn get_by_id_returns_matching_organization() {
75        let id = OrganizationId::new(Uuid::new_v4());
76        let other = OrganizationId::new(Uuid::new_v4());
77        let client = client_with(&[organization(id, "Org"), organization(other, "Other")]).await;
78
79        let result = client.get_by_id(id).await.unwrap();
80
81        assert_eq!(result, Some(organization(id, "Org")));
82    }
83
84    #[tokio::test]
85    async fn get_by_id_returns_none_when_missing() {
86        let client = client_with(&[]).await;
87
88        let result = client
89            .get_by_id(OrganizationId::new(Uuid::new_v4()))
90            .await
91            .unwrap();
92
93        assert_eq!(result, None);
94    }
95
96    #[tokio::test]
97    async fn get_all_returns_every_organization() {
98        let first = organization(OrganizationId::new(Uuid::new_v4()), "First");
99        let second = organization(OrganizationId::new(Uuid::new_v4()), "Second");
100        let client = client_with(&[first.clone(), second.clone()]).await;
101
102        let mut result = client.get_all().await.unwrap();
103        result.sort_by(|a, b| a.details.name.cmp(&b.details.name));
104
105        assert_eq!(result, vec![first, second]);
106    }
107
108    #[tokio::test]
109    async fn errors_without_a_repository() {
110        let client = OrganizationsClient { repository: None };
111
112        assert!(client.get_all().await.is_err());
113        assert!(
114            client
115                .get_by_id(OrganizationId::new(Uuid::new_v4()))
116                .await
117                .is_err()
118        );
119    }
120}