bitwarden_sm/projects/
create.rs1use 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 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}