things3_core/database/
validators.rs1use crate::error::{Result as ThingsResult, ThingsError};
7use sqlx::SqlitePool;
8use tracing::instrument;
9use uuid::Uuid;
10
11#[instrument(skip(pool))]
17pub async fn validate_task_exists(pool: &SqlitePool, uuid: &Uuid) -> ThingsResult<()> {
18 let exists = sqlx::query("SELECT 1 FROM TMTask WHERE uuid = ? AND trashed = 0")
19 .bind(uuid.to_string())
20 .fetch_optional(pool)
21 .await
22 .map_err(|e| ThingsError::unknown(format!("Failed to validate task: {e}")))?
23 .is_some();
24
25 if !exists {
26 return Err(ThingsError::unknown(format!("Task not found: {uuid}")));
27 }
28 Ok(())
29}
30
31#[instrument(skip(pool))]
37pub async fn validate_project_exists(pool: &SqlitePool, uuid: &Uuid) -> ThingsResult<()> {
38 let exists = sqlx::query("SELECT 1 FROM TMTask WHERE uuid = ? AND type = 1 AND trashed = 0")
39 .bind(uuid.to_string())
40 .fetch_optional(pool)
41 .await
42 .map_err(|e| ThingsError::unknown(format!("Failed to validate project: {e}")))?
43 .is_some();
44
45 if !exists {
46 return Err(ThingsError::ProjectNotFound {
47 uuid: uuid.to_string(),
48 });
49 }
50 Ok(())
51}
52
53#[instrument(skip(pool))]
59pub async fn validate_area_exists(pool: &SqlitePool, uuid: &Uuid) -> ThingsResult<()> {
60 let exists = sqlx::query("SELECT 1 FROM TMArea WHERE uuid = ?")
61 .bind(uuid.to_string())
62 .fetch_optional(pool)
63 .await
64 .map_err(|e| ThingsError::unknown(format!("Failed to validate area: {e}")))?
65 .is_some();
66
67 if !exists {
68 return Err(ThingsError::unknown(format!("Area not found: {uuid}")));
69 }
70 Ok(())
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[cfg(feature = "test-utils")]
78 #[tokio::test]
79 async fn test_validate_nonexistent_task() {
80 use crate::test_utils::create_test_database;
81 use tempfile::NamedTempFile;
82
83 let temp_file = NamedTempFile::new().unwrap();
84 let db_path = temp_file.path();
85 create_test_database(db_path).await.unwrap();
86
87 let pool = sqlx::SqlitePool::connect(&format!("sqlite://{}", db_path.display()))
88 .await
89 .unwrap();
90
91 let nonexistent_uuid = Uuid::new_v4();
92 let result = validate_task_exists(&pool, &nonexistent_uuid).await;
93
94 assert!(result.is_err());
95 assert!(result.unwrap_err().to_string().contains("Task not found"));
96 }
97
98 #[cfg(feature = "test-utils")]
99 #[tokio::test]
100 async fn test_validate_nonexistent_project() {
101 use crate::test_utils::create_test_database;
102 use tempfile::NamedTempFile;
103
104 let temp_file = NamedTempFile::new().unwrap();
105 let db_path = temp_file.path();
106 create_test_database(db_path).await.unwrap();
107
108 let pool = sqlx::SqlitePool::connect(&format!("sqlite://{}", db_path.display()))
109 .await
110 .unwrap();
111
112 let nonexistent_uuid = Uuid::new_v4();
113 let result = validate_project_exists(&pool, &nonexistent_uuid).await;
114
115 assert!(result.is_err());
116 assert!(result
117 .unwrap_err()
118 .to_string()
119 .contains("Project not found"));
120 }
121
122 #[cfg(feature = "test-utils")]
123 #[tokio::test]
124 async fn test_validate_nonexistent_area() {
125 use crate::test_utils::create_test_database;
126 use tempfile::NamedTempFile;
127
128 let temp_file = NamedTempFile::new().unwrap();
129 let db_path = temp_file.path();
130 create_test_database(db_path).await.unwrap();
131
132 let pool = sqlx::SqlitePool::connect(&format!("sqlite://{}", db_path.display()))
133 .await
134 .unwrap();
135
136 let nonexistent_uuid = Uuid::new_v4();
137 let result = validate_area_exists(&pool, &nonexistent_uuid).await;
138
139 assert!(result.is_err());
140 assert!(result.unwrap_err().to_string().contains("Area not found"));
141 }
142}