skp_validator_core/
context.rs1use std::any::Any;
9use std::collections::HashMap;
10use std::sync::Arc;
11
12#[cfg(feature = "serde")]
13use serde_json::Value as JsonValue;
14
15#[derive(Debug, Clone, Default)]
32pub struct ValidationContext {
33 #[cfg(feature = "serde")]
35 field_values: HashMap<String, JsonValue>,
36
37 #[cfg(not(feature = "serde"))]
39 field_values: HashMap<String, String>,
40
41 metadata: HashMap<String, String>,
43
44 locale: String,
46
47 fail_fast: bool,
49
50 custom_data: Option<Arc<dyn Any + Send + Sync>>,
52}
53
54impl ValidationContext {
55 pub fn new() -> Self {
57 Self {
58 field_values: HashMap::new(),
59 metadata: HashMap::new(),
60 locale: "en".to_string(),
61 fail_fast: false,
62 custom_data: None,
63 }
64 }
65
66 pub fn with_locale(mut self, locale: impl Into<String>) -> Self {
68 self.locale = locale.into();
69 self
70 }
71
72 pub fn with_fail_fast(mut self, fail_fast: bool) -> Self {
74 self.fail_fast = fail_fast;
75 self
76 }
77
78 pub fn with_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
80 self.metadata.insert(key.into(), value.into());
81 self
82 }
83
84 pub fn with_custom_data<T: Any + Send + Sync>(mut self, data: T) -> Self {
86 self.custom_data = Some(Arc::new(data));
87 self
88 }
89
90 pub fn locale(&self) -> &str {
92 &self.locale
93 }
94
95 pub fn is_fail_fast(&self) -> bool {
97 self.fail_fast
98 }
99
100 pub fn get_meta(&self, key: &str) -> Option<&str> {
102 self.metadata.get(key).map(|s| s.as_str())
103 }
104
105 pub fn get_custom_data<T: Any + Send + Sync>(&self) -> Option<&T> {
107 self.custom_data
108 .as_ref()
109 .and_then(|d| d.downcast_ref::<T>())
110 }
111
112 #[cfg(feature = "serde")]
116 pub fn from_json(json: &JsonValue) -> Self {
117 let mut field_values = HashMap::new();
118
119 if let Some(obj) = json.as_object() {
120 for (key, value) in obj {
121 field_values.insert(key.clone(), value.clone());
122 }
123 }
124
125 Self {
126 field_values,
127 metadata: HashMap::new(),
128 locale: "en".to_string(),
129 fail_fast: false,
130 custom_data: None,
131 }
132 }
133
134 #[cfg(feature = "serde")]
136 pub fn from_serde<T: serde::Serialize>(data: &T) -> Result<Self, serde_json::Error> {
137 let json = serde_json::to_value(data)?;
138 Ok(Self::from_json(&json))
139 }
140
141 #[cfg(feature = "serde")]
143 pub fn get_field(&self, name: &str) -> Option<&JsonValue> {
144 self.field_values.get(name)
145 }
146
147 #[cfg(feature = "serde")]
149 pub fn set_field(&mut self, name: impl Into<String>, value: JsonValue) {
150 self.field_values.insert(name.into(), value);
151 }
152
153 #[cfg(feature = "serde")]
155 pub fn with_field(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
156 self.field_values.insert(name.into(), JsonValue::String(value.into()));
157 self
158 }
159
160 #[cfg(feature = "serde")]
162 pub fn get_string(&self, name: &str) -> Option<&str> {
163 self.get_field(name)?.as_str()
164 }
165
166 #[cfg(feature = "serde")]
168 pub fn get_i64(&self, name: &str) -> Option<i64> {
169 self.get_field(name)?.as_i64()
170 }
171
172 #[cfg(feature = "serde")]
174 pub fn get_f64(&self, name: &str) -> Option<f64> {
175 self.get_field(name)?.as_f64()
176 }
177
178 #[cfg(feature = "serde")]
180 pub fn get_bool(&self, name: &str) -> Option<bool> {
181 self.get_field(name)?.as_bool()
182 }
183
184 #[cfg(feature = "serde")]
186 pub fn has_value(&self, name: &str) -> bool {
187 if let Some(value) = self.get_field(name) {
188 !value.is_null()
189 && match value {
190 JsonValue::String(s) => !s.trim().is_empty(),
191 JsonValue::Array(arr) => !arr.is_empty(),
192 JsonValue::Object(obj) => !obj.is_empty(),
193 _ => true,
194 }
195 } else {
196 false
197 }
198 }
199
200 #[cfg(feature = "serde")]
202 pub fn is_empty(&self, name: &str) -> bool {
203 !self.has_value(name)
204 }
205
206 pub fn field_names(&self) -> impl Iterator<Item = &String> {
208 self.field_values.keys()
209 }
210
211 #[cfg(not(feature = "serde"))]
215 pub fn set_field(&mut self, name: impl Into<String>, value: impl Into<String>) {
216 self.field_values.insert(name.into(), value.into());
217 }
218
219 #[cfg(not(feature = "serde"))]
221 pub fn get_string(&self, name: &str) -> Option<&str> {
222 self.field_values.get(name).map(|s| s.as_str())
223 }
224
225 #[cfg(not(feature = "serde"))]
227 pub fn has_value(&self, name: &str) -> bool {
228 self.field_values.get(name).map(|s| !s.is_empty()).unwrap_or(false)
229 }
230}
231
232pub struct ValidationContextBuilder<C> {
234 context: ValidationContext,
235 custom: Option<C>,
236}
237
238impl<C: Any + Send + Sync> ValidationContextBuilder<C> {
239 pub fn new() -> Self {
241 Self {
242 context: ValidationContext::new(),
243 custom: None,
244 }
245 }
246
247 pub fn locale(mut self, locale: impl Into<String>) -> Self {
249 self.context.locale = locale.into();
250 self
251 }
252
253 pub fn fail_fast(mut self, fail_fast: bool) -> Self {
255 self.context.fail_fast = fail_fast;
256 self
257 }
258
259 pub fn meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
261 self.context.metadata.insert(key.into(), value.into());
262 self
263 }
264
265 pub fn custom(mut self, custom: C) -> Self {
267 self.custom = Some(custom);
268 self
269 }
270
271 pub fn build(mut self) -> ValidationContext {
273 if let Some(custom) = self.custom {
274 self.context.custom_data = Some(Arc::new(custom));
275 }
276 self.context
277 }
278}
279
280impl<C: Any + Send + Sync> Default for ValidationContextBuilder<C> {
281 fn default() -> Self {
282 Self::new()
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_context_creation() {
292 let ctx = ValidationContext::new()
293 .with_locale("hi")
294 .with_meta("request_id", "123");
295
296 assert_eq!(ctx.locale(), "hi");
297 assert_eq!(ctx.get_meta("request_id"), Some("123"));
298 }
299
300 #[test]
301 fn test_custom_data() {
302 #[derive(Debug, Clone)]
303 struct MyContext {
304 user_id: u64,
305 }
306
307 let ctx = ValidationContext::new().with_custom_data(MyContext { user_id: 42 });
308
309 let my_ctx = ctx.get_custom_data::<MyContext>().unwrap();
310 assert_eq!(my_ctx.user_id, 42);
311 }
312
313 #[cfg(feature = "serde")]
314 #[test]
315 fn test_from_json() {
316 let json = serde_json::json!({
317 "name": "John",
318 "age": 30,
319 "active": true
320 });
321
322 let ctx = ValidationContext::from_json(&json);
323
324 assert_eq!(ctx.get_string("name"), Some("John"));
325 assert_eq!(ctx.get_i64("age"), Some(30));
326 assert_eq!(ctx.get_bool("active"), Some(true));
327 }
328
329 #[cfg(feature = "serde")]
330 #[test]
331 fn test_has_value() {
332 let json = serde_json::json!({
333 "name": "John",
334 "empty": "",
335 "null": null
336 });
337
338 let ctx = ValidationContext::from_json(&json);
339
340 assert!(ctx.has_value("name"));
341 assert!(!ctx.has_value("empty"));
342 assert!(!ctx.has_value("null"));
343 assert!(!ctx.has_value("missing"));
344 }
345}