bitwarden_sm/projects/
create.rs

1use bitwarden_api_api::models::ProjectCreateRequestModel;
2use bitwarden_core::{Client, OrganizationId, key_management::SymmetricKeyId};
3use bitwarden_crypto::PrimitiveEncryptable;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7use validator::Validate;
8
9use crate::{
10    error::{SecretsManagerError, validate_only_whitespaces},
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(OrganizationId::new(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 = config
44        .api_client
45        .projects_api()
46        .create(input.organization_id, project)
47        .await?;
48
49    ProjectResponse::process_response(res, &mut key_store.context())
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    async fn create_project(name: String) -> Result<ProjectResponse, SecretsManagerError> {
57        let input = ProjectCreateRequest {
58            organization_id: Uuid::new_v4(),
59            name,
60        };
61
62        super::create_project(&Client::new(None), &input).await
63    }
64
65    #[tokio::test]
66    async fn test_create_project_request_name_empty_string() {
67        let response = create_project("".into()).await;
68        assert!(response.is_err());
69        assert_eq!(
70            response.err().unwrap().to_string(),
71            "name must not be empty"
72        );
73    }
74
75    #[tokio::test]
76    async fn test_create_project_request_name_all_whitespaces_space() {
77        let response = create_project(" ".into()).await;
78        assert!(response.is_err());
79        assert_eq!(
80            response.err().unwrap().to_string(),
81            "name must not contain only whitespaces"
82        );
83    }
84
85    #[tokio::test]
86    async fn test_create_project_request_name_all_whitespaces_tab() {
87        let response = create_project("\t".into()).await;
88        assert!(response.is_err());
89        assert_eq!(
90            response.err().unwrap().to_string(),
91            "name must not contain only whitespaces"
92        );
93    }
94
95    #[tokio::test]
96    async fn test_create_project_request_name_all_whitespaces_newline() {
97        let response = create_project("\n".into()).await;
98        assert!(response.is_err());
99        assert_eq!(
100            response.err().unwrap().to_string(),
101            "name must not contain only whitespaces"
102        );
103    }
104
105    #[tokio::test]
106    async fn test_create_project_request_name_all_whitespaces_combined() {
107        let response = create_project(" \t\n".into()).await;
108        assert!(response.is_err());
109        assert_eq!(
110            response.err().unwrap().to_string(),
111            "name must not contain only whitespaces"
112        );
113    }
114
115    #[tokio::test]
116    async fn test_create_project_request_name_501_character_length() {
117        let response = create_project("a".repeat(501)).await;
118        assert!(response.is_err());
119        assert_eq!(
120            response.err().unwrap().to_string(),
121            "name must not exceed 500 characters in length"
122        );
123    }
124}