rustapi_validate/v2/
context.rs1use async_trait::async_trait;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7#[async_trait]
9pub trait DatabaseValidator: Send + Sync {
10 async fn exists(&self, table: &str, column: &str, value: &str) -> Result<bool, String>;
12
13 async fn is_unique(&self, table: &str, column: &str, value: &str) -> Result<bool, String>;
15
16 async fn is_unique_except(
18 &self,
19 table: &str,
20 column: &str,
21 value: &str,
22 except_id: &str,
23 ) -> Result<bool, String>;
24}
25
26#[async_trait]
28pub trait HttpValidator: Send + Sync {
29 async fn validate(&self, endpoint: &str, value: &str) -> Result<bool, String>;
31}
32
33#[async_trait]
35pub trait CustomValidator: Send + Sync {
36 async fn validate(&self, value: &str) -> Result<bool, String>;
38}
39
40#[derive(Default)]
57pub struct ValidationContext {
58 database: Option<Arc<dyn DatabaseValidator>>,
59 http: Option<Arc<dyn HttpValidator>>,
60 custom: HashMap<String, Arc<dyn CustomValidator>>,
61 exclude_id: Option<String>,
63}
64
65impl ValidationContext {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn database(&self) -> Option<&Arc<dyn DatabaseValidator>> {
73 self.database.as_ref()
74 }
75
76 pub fn http(&self) -> Option<&Arc<dyn HttpValidator>> {
78 self.http.as_ref()
79 }
80
81 pub fn custom(&self, name: &str) -> Option<&Arc<dyn CustomValidator>> {
83 self.custom.get(name)
84 }
85
86 pub fn exclude_id(&self) -> Option<&str> {
88 self.exclude_id.as_deref()
89 }
90
91 pub fn builder() -> ValidationContextBuilder {
93 ValidationContextBuilder::new()
94 }
95}
96
97impl std::fmt::Debug for ValidationContext {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("ValidationContext")
100 .field("has_database", &self.database.is_some())
101 .field("has_http", &self.http.is_some())
102 .field("custom_validators", &self.custom.keys().collect::<Vec<_>>())
103 .field("exclude_id", &self.exclude_id)
104 .finish()
105 }
106}
107
108#[derive(Default)]
110pub struct ValidationContextBuilder {
111 database: Option<Arc<dyn DatabaseValidator>>,
112 http: Option<Arc<dyn HttpValidator>>,
113 custom: HashMap<String, Arc<dyn CustomValidator>>,
114 exclude_id: Option<String>,
115}
116
117impl ValidationContextBuilder {
118 pub fn new() -> Self {
120 Self::default()
121 }
122
123 pub fn database(mut self, validator: impl DatabaseValidator + 'static) -> Self {
125 self.database = Some(Arc::new(validator));
126 self
127 }
128
129 pub fn database_arc(mut self, validator: Arc<dyn DatabaseValidator>) -> Self {
131 self.database = Some(validator);
132 self
133 }
134
135 pub fn http(mut self, validator: impl HttpValidator + 'static) -> Self {
137 self.http = Some(Arc::new(validator));
138 self
139 }
140
141 pub fn http_arc(mut self, validator: Arc<dyn HttpValidator>) -> Self {
143 self.http = Some(validator);
144 self
145 }
146
147 pub fn custom(
149 mut self,
150 name: impl Into<String>,
151 validator: impl CustomValidator + 'static,
152 ) -> Self {
153 self.custom.insert(name.into(), Arc::new(validator));
154 self
155 }
156
157 pub fn custom_arc(
159 mut self,
160 name: impl Into<String>,
161 validator: Arc<dyn CustomValidator>,
162 ) -> Self {
163 self.custom.insert(name.into(), validator);
164 self
165 }
166
167 pub fn exclude_id(mut self, id: impl Into<String>) -> Self {
169 self.exclude_id = Some(id.into());
170 self
171 }
172
173 pub fn build(self) -> ValidationContext {
175 ValidationContext {
176 database: self.database,
177 http: self.http,
178 custom: self.custom,
179 exclude_id: self.exclude_id,
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 struct MockDbValidator;
189
190 #[async_trait]
191 impl DatabaseValidator for MockDbValidator {
192 async fn exists(&self, _table: &str, _column: &str, _value: &str) -> Result<bool, String> {
193 Ok(true)
194 }
195
196 async fn is_unique(
197 &self,
198 _table: &str,
199 _column: &str,
200 _value: &str,
201 ) -> Result<bool, String> {
202 Ok(true)
203 }
204
205 async fn is_unique_except(
206 &self,
207 _table: &str,
208 _column: &str,
209 _value: &str,
210 _except_id: &str,
211 ) -> Result<bool, String> {
212 Ok(true)
213 }
214 }
215
216 #[test]
217 fn context_builder() {
218 let ctx = ValidationContextBuilder::new()
219 .database(MockDbValidator)
220 .exclude_id("123")
221 .build();
222
223 assert!(ctx.database().is_some());
224 assert!(ctx.http().is_none());
225 assert_eq!(ctx.exclude_id(), Some("123"));
226 }
227
228 #[test]
229 fn empty_context() {
230 let ctx = ValidationContext::new();
231 assert!(ctx.database().is_none());
232 assert!(ctx.http().is_none());
233 assert!(ctx.exclude_id().is_none());
234 }
235}