bitwarden_sm/projects/
create.rs

1use bitwarden_api_api::models::ProjectCreateRequestModel;
2use bitwarden_core::{key_management::SymmetricKeyId, Client};
3use bitwarden_crypto::Encryptable;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7use validator::Validate;
8
9use crate::{
10    error::{validate_only_whitespaces, SecretsManagerError},
11    projects::ProjectResponse,
12};
13
14#[allow(missing_docs)]
15#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)]
16#[serde(rename_all = "camelCase", deny_unknown_fields)]
17pub struct ProjectCreateRequest {
18    /// Organization where the project will be created
19    pub organization_id: Uuid,
20    #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))]
21    pub name: String,
22}
23
24pub(crate) async fn create_project(
25    client: &Client,
26    input: &ProjectCreateRequest,
27) -> Result<ProjectResponse, SecretsManagerError> {
28    input.validate()?;
29
30    let key_store = client.internal.get_key_store();
31    let key = SymmetricKeyId::Organization(input.organization_id);
32
33    let project = Some(ProjectCreateRequestModel {
34        name: input
35            .name
36            .clone()
37            .trim()
38            .encrypt(&mut key_store.context(), key)?
39            .to_string(),
40    });
41
42    let config = client.internal.get_api_configurations().await;
43    let res = bitwarden_api_api::apis::projects_api::organizations_organization_id_projects_post(
44        &config.api,
45        input.organization_id,
46        project,
47    )
48    .await?;
49
50    ProjectResponse::process_response(res, &mut key_store.context())
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    async fn create_project(name: String) -> Result<ProjectResponse, SecretsManagerError> {
58        let input = ProjectCreateRequest {
59            organization_id: Uuid::new_v4(),
60            name,
61        };
62
63        super::create_project(&Client::new(None), &input).await
64    }
65
66    #[tokio::test]
67    async fn test_create_project_request_name_empty_string() {
68        let response = create_project("".into()).await;
69        assert!(response.is_err());
70        assert_eq!(
71            response.err().unwrap().to_string(),
72            "name must not be empty"
73        );
74    }
75
76    #[tokio::test]
77    async fn test_create_project_request_name_all_whitespaces_space() {
78        let response = create_project(" ".into()).await;
79        assert!(response.is_err());
80        assert_eq!(
81            response.err().unwrap().to_string(),
82            "name must not contain only whitespaces"
83        );
84    }
85
86    #[tokio::test]
87    async fn test_create_project_request_name_all_whitespaces_tab() {
88        let response = create_project("\t".into()).await;
89        assert!(response.is_err());
90        assert_eq!(
91            response.err().unwrap().to_string(),
92            "name must not contain only whitespaces"
93        );
94    }
95
96    #[tokio::test]
97    async fn test_create_project_request_name_all_whitespaces_newline() {
98        let response = create_project("\n".into()).await;
99        assert!(response.is_err());
100        assert_eq!(
101            response.err().unwrap().to_string(),
102            "name must not contain only whitespaces"
103        );
104    }
105
106    #[tokio::test]
107    async fn test_create_project_request_name_all_whitespaces_combined() {
108        let response = create_project(" \t\n".into()).await;
109        assert!(response.is_err());
110        assert_eq!(
111            response.err().unwrap().to_string(),
112            "name must not contain only whitespaces"
113        );
114    }
115
116    #[tokio::test]
117    async fn test_create_project_request_name_501_character_length() {
118        let response = create_project("a".repeat(501)).await;
119        assert!(response.is_err());
120        assert_eq!(
121            response.err().unwrap().to_string(),
122            "name must not exceed 500 characters in length"
123        );
124    }
125}