-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcontext.rs
More file actions
251 lines (217 loc) · 6.89 KB
/
context.rs
File metadata and controls
251 lines (217 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
//! Validation context for async operations.
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
/// Trait for database validation operations.
#[async_trait]
pub trait DatabaseValidator: Send + Sync {
/// Check if a value exists in a table column.
async fn exists(&self, table: &str, column: &str, value: &str) -> Result<bool, String>;
/// Check if a value is unique in a table column.
async fn is_unique(&self, table: &str, column: &str, value: &str) -> Result<bool, String>;
/// Check if a value is unique, excluding a specific ID (for updates).
async fn is_unique_except(
&self,
table: &str,
column: &str,
value: &str,
except_id: &str,
) -> Result<bool, String>;
}
/// Trait for HTTP/API validation operations.
#[async_trait]
pub trait HttpValidator: Send + Sync {
/// Validate a value against an external API endpoint.
async fn validate(&self, endpoint: &str, value: &str) -> Result<bool, String>;
}
/// Trait for custom async validators.
#[async_trait]
pub trait CustomValidator: Send + Sync {
/// Validate a value with custom logic.
async fn validate(&self, value: &str) -> Result<bool, String>;
}
/// Context for async validation operations.
///
/// Provides access to database, HTTP, and custom validators for async validation rules.
///
/// ## Example
///
/// ```rust,ignore
/// use rustapi_validate::v2::prelude::*;
///
/// let ctx = ValidationContextBuilder::new()
/// .database(my_db_validator)
/// .http(my_http_client)
/// .build();
///
/// user.validate_async(&ctx).await?;
/// ```
#[derive(Clone, Default)]
pub struct ValidationContext {
database: Option<Arc<dyn DatabaseValidator>>,
http: Option<Arc<dyn HttpValidator>>,
custom: HashMap<String, Arc<dyn CustomValidator>>,
/// ID to exclude from uniqueness checks (for updates)
exclude_id: Option<String>,
/// Locale for error messages (e.g. "en", "tr")
locale: Option<String>,
}
impl ValidationContext {
/// Create a new empty validation context.
pub fn new() -> Self {
Self::default()
}
/// Get the database validator if configured.
pub fn database(&self) -> Option<&Arc<dyn DatabaseValidator>> {
self.database.as_ref()
}
/// Get the HTTP validator if configured.
pub fn http(&self) -> Option<&Arc<dyn HttpValidator>> {
self.http.as_ref()
}
/// Get a custom validator by name.
pub fn custom(&self, name: &str) -> Option<&Arc<dyn CustomValidator>> {
self.custom.get(name)
}
/// Get the locale.
pub fn locale(&self) -> Option<&str> {
self.locale.as_deref()
}
/// Get the ID to exclude from uniqueness checks.
pub fn exclude_id(&self) -> Option<&str> {
self.exclude_id.as_deref()
}
/// Create a builder for constructing a validation context.
pub fn builder() -> ValidationContextBuilder {
ValidationContextBuilder::new()
}
}
impl std::fmt::Debug for ValidationContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ValidationContext")
.field("has_database", &self.database.is_some())
.field("has_http", &self.http.is_some())
.field("custom_validators", &self.custom.keys().collect::<Vec<_>>())
.field("exclude_id", &self.exclude_id)
.field("locale", &self.locale)
.finish()
}
}
/// Builder for constructing a `ValidationContext`.
#[derive(Clone, Default)]
pub struct ValidationContextBuilder {
database: Option<Arc<dyn DatabaseValidator>>,
http: Option<Arc<dyn HttpValidator>>,
custom: HashMap<String, Arc<dyn CustomValidator>>,
exclude_id: Option<String>,
locale: Option<String>,
}
impl ValidationContextBuilder {
/// Create a new builder.
pub fn new() -> Self {
Self::default()
}
/// Set the database validator.
pub fn database(mut self, validator: impl DatabaseValidator + 'static) -> Self {
self.database = Some(Arc::new(validator));
self
}
/// Set the database validator from an Arc.
pub fn database_arc(mut self, validator: Arc<dyn DatabaseValidator>) -> Self {
self.database = Some(validator);
self
}
/// Set the HTTP validator.
pub fn http(mut self, validator: impl HttpValidator + 'static) -> Self {
self.http = Some(Arc::new(validator));
self
}
/// Set the HTTP validator from an Arc.
pub fn http_arc(mut self, validator: Arc<dyn HttpValidator>) -> Self {
self.http = Some(validator);
self
}
/// Add a custom validator.
pub fn custom(
mut self,
name: impl Into<String>,
validator: impl CustomValidator + 'static,
) -> Self {
self.custom.insert(name.into(), Arc::new(validator));
self
}
/// Add a custom validator from an Arc.
pub fn custom_arc(
mut self,
name: impl Into<String>,
validator: Arc<dyn CustomValidator>,
) -> Self {
self.custom.insert(name.into(), validator);
self
}
/// Set the ID to exclude from uniqueness checks (for updates).
pub fn exclude_id(mut self, id: impl Into<String>) -> Self {
self.exclude_id = Some(id.into());
self
}
/// Set the locale.
pub fn locale(mut self, locale: impl Into<String>) -> Self {
self.locale = Some(locale.into());
self
}
/// Build the validation context.
pub fn build(self) -> ValidationContext {
ValidationContext {
database: self.database,
http: self.http,
custom: self.custom,
exclude_id: self.exclude_id,
locale: self.locale,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockDbValidator;
#[async_trait]
impl DatabaseValidator for MockDbValidator {
async fn exists(&self, _table: &str, _column: &str, _value: &str) -> Result<bool, String> {
Ok(true)
}
async fn is_unique(
&self,
_table: &str,
_column: &str,
_value: &str,
) -> Result<bool, String> {
Ok(true)
}
async fn is_unique_except(
&self,
_table: &str,
_column: &str,
_value: &str,
_except_id: &str,
) -> Result<bool, String> {
Ok(true)
}
}
#[test]
fn context_builder() {
let ctx = ValidationContextBuilder::new()
.database(MockDbValidator)
.exclude_id("123")
.build();
assert!(ctx.database().is_some());
assert!(ctx.http().is_none());
assert_eq!(ctx.exclude_id(), Some("123"));
}
#[test]
fn empty_context() {
let ctx = ValidationContext::new();
assert!(ctx.database().is_none());
assert!(ctx.http().is_none());
assert!(ctx.exclude_id().is_none());
}
}