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