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 locale: Option<String>,
65}
66
67impl ValidationContext {
68 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub fn database(&self) -> Option<&Arc<dyn DatabaseValidator>> {
75 self.database.as_ref()
76 }
77
78 pub fn http(&self) -> Option<&Arc<dyn HttpValidator>> {
80 self.http.as_ref()
81 }
82
83 pub fn custom(&self, name: &str) -> Option<&Arc<dyn CustomValidator>> {
85 self.custom.get(name)
86 }
87
88 pub fn locale(&self) -> Option<&str> {
90 self.locale.as_deref()
91 }
92
93 pub fn exclude_id(&self) -> Option<&str> {
95 self.exclude_id.as_deref()
96 }
97
98 pub fn builder() -> ValidationContextBuilder {
100 ValidationContextBuilder::new()
101 }
102}
103
104impl std::fmt::Debug for ValidationContext {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.debug_struct("ValidationContext")
107 .field("has_database", &self.database.is_some())
108 .field("has_http", &self.http.is_some())
109 .field("custom_validators", &self.custom.keys().collect::<Vec<_>>())
110 .field("exclude_id", &self.exclude_id)
111 .field("locale", &self.locale)
112 .finish()
113 }
114}
115
116#[derive(Default)]
118pub struct ValidationContextBuilder {
119 database: Option<Arc<dyn DatabaseValidator>>,
120 http: Option<Arc<dyn HttpValidator>>,
121 custom: HashMap<String, Arc<dyn CustomValidator>>,
122 exclude_id: Option<String>,
123 locale: Option<String>,
124}
125
126impl ValidationContextBuilder {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn database(mut self, validator: impl DatabaseValidator + 'static) -> Self {
134 self.database = Some(Arc::new(validator));
135 self
136 }
137
138 pub fn database_arc(mut self, validator: Arc<dyn DatabaseValidator>) -> Self {
140 self.database = Some(validator);
141 self
142 }
143
144 pub fn http(mut self, validator: impl HttpValidator + 'static) -> Self {
146 self.http = Some(Arc::new(validator));
147 self
148 }
149
150 pub fn http_arc(mut self, validator: Arc<dyn HttpValidator>) -> Self {
152 self.http = Some(validator);
153 self
154 }
155
156 pub fn custom(
158 mut self,
159 name: impl Into<String>,
160 validator: impl CustomValidator + 'static,
161 ) -> Self {
162 self.custom.insert(name.into(), Arc::new(validator));
163 self
164 }
165
166 pub fn custom_arc(
168 mut self,
169 name: impl Into<String>,
170 validator: Arc<dyn CustomValidator>,
171 ) -> Self {
172 self.custom.insert(name.into(), validator);
173 self
174 }
175
176 pub fn exclude_id(mut self, id: impl Into<String>) -> Self {
178 self.exclude_id = Some(id.into());
179 self
180 }
181
182 pub fn locale(mut self, locale: impl Into<String>) -> Self {
184 self.locale = Some(locale.into());
185 self
186 }
187
188 pub fn build(self) -> ValidationContext {
190 ValidationContext {
191 database: self.database,
192 http: self.http,
193 custom: self.custom,
194 exclude_id: self.exclude_id,
195 locale: self.locale,
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 struct MockDbValidator;
205
206 #[async_trait]
207 impl DatabaseValidator for MockDbValidator {
208 async fn exists(&self, _table: &str, _column: &str, _value: &str) -> Result<bool, String> {
209 Ok(true)
210 }
211
212 async fn is_unique(
213 &self,
214 _table: &str,
215 _column: &str,
216 _value: &str,
217 ) -> Result<bool, String> {
218 Ok(true)
219 }
220
221 async fn is_unique_except(
222 &self,
223 _table: &str,
224 _column: &str,
225 _value: &str,
226 _except_id: &str,
227 ) -> Result<bool, String> {
228 Ok(true)
229 }
230 }
231
232 #[test]
233 fn context_builder() {
234 let ctx = ValidationContextBuilder::new()
235 .database(MockDbValidator)
236 .exclude_id("123")
237 .build();
238
239 assert!(ctx.database().is_some());
240 assert!(ctx.http().is_none());
241 assert_eq!(ctx.exclude_id(), Some("123"));
242 }
243
244 #[test]
245 fn empty_context() {
246 let ctx = ValidationContext::new();
247 assert!(ctx.database().is_none());
248 assert!(ctx.http().is_none());
249 assert!(ctx.exclude_id().is_none());
250 }
251}