1use crate::error::{Result, XervError};
7use serde::{Deserialize, Serialize};
8use serde_json::Value as JsonValue;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct Value(pub JsonValue);
17
18impl Value {
19 pub fn null() -> Self {
21 Self(JsonValue::Null)
22 }
23
24 pub fn bool(v: bool) -> Self {
26 Self(JsonValue::Bool(v))
27 }
28
29 pub fn int(v: i64) -> Self {
31 Self(JsonValue::Number(v.into()))
32 }
33
34 pub fn float(v: f64) -> Self {
36 Self(serde_json::Number::from_f64(v).map_or(JsonValue::Null, JsonValue::Number))
37 }
38
39 pub fn string(v: impl Into<String>) -> Self {
41 Self(JsonValue::String(v.into()))
42 }
43
44 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
46 if bytes.is_empty() {
47 return Ok(Self::null());
48 }
49 serde_json::from_slice(bytes)
50 .map(Self)
51 .map_err(|e| XervError::Serialization(format!("Failed to parse value: {}", e)))
52 }
53
54 pub fn to_bytes(&self) -> Result<Vec<u8>> {
56 serde_json::to_vec(&self.0)
57 .map_err(|e| XervError::Serialization(format!("Failed to serialize value: {}", e)))
58 }
59
60 pub fn is_null(&self) -> bool {
62 self.0.is_null()
63 }
64
65 pub fn get_field(&self, path: &str) -> Option<Value> {
74 let path = path.strip_prefix("$.").unwrap_or(path);
76
77 let mut current = &self.0;
78 for part in path.split('.') {
79 if let Some((field, idx_str)) = part.split_once('[') {
81 current = current.get(field)?;
82 let idx_str = idx_str.strip_suffix(']')?;
83 let idx: usize = idx_str.parse().ok()?;
84 current = current.get(idx)?;
85 } else {
86 current = current.get(part)?;
87 }
88 }
89 Some(Value(current.clone()))
90 }
91
92 pub fn get_string(&self, path: &str) -> Option<String> {
94 self.get_field(path).and_then(|v| v.as_string())
95 }
96
97 pub fn get_f64(&self, path: &str) -> Option<f64> {
99 self.get_field(path).and_then(|v| v.as_f64())
100 }
101
102 pub fn get_bool(&self, path: &str) -> Option<bool> {
104 self.get_field(path).and_then(|v| v.as_bool())
105 }
106
107 pub fn as_string(&self) -> Option<String> {
109 match &self.0 {
110 JsonValue::String(s) => Some(s.clone()),
111 JsonValue::Number(n) => Some(n.to_string()),
112 JsonValue::Bool(b) => Some(b.to_string()),
113 JsonValue::Null => None,
114 _ => Some(self.0.to_string()),
115 }
116 }
117
118 pub fn as_f64(&self) -> Option<f64> {
120 match &self.0 {
121 JsonValue::Number(n) => n.as_f64(),
122 JsonValue::String(s) => s.parse().ok(),
123 _ => None,
124 }
125 }
126
127 pub fn as_bool(&self) -> Option<bool> {
129 match &self.0 {
130 JsonValue::Bool(b) => Some(*b),
131 JsonValue::String(s) => match s.to_lowercase().as_str() {
132 "true" | "1" | "yes" => Some(true),
133 "false" | "0" | "no" => Some(false),
134 _ => None,
135 },
136 JsonValue::Number(n) => Some(n.as_f64().map_or(false, |v| v != 0.0)),
137 JsonValue::Null => Some(false),
138 _ => None,
139 }
140 }
141
142 pub fn equals_str(&self, other: &str) -> bool {
144 self.as_string().map_or(false, |s| s == other)
145 }
146
147 pub fn field_equals(&self, path: &str, value: &str) -> bool {
149 self.get_field(path).map_or(false, |v| v.equals_str(value))
150 }
151
152 pub fn field_greater_than(&self, path: &str, threshold: f64) -> bool {
154 self.get_f64(path).map_or(false, |v| v > threshold)
155 }
156
157 pub fn field_less_than(&self, path: &str, threshold: f64) -> bool {
159 self.get_f64(path).map_or(false, |v| v < threshold)
160 }
161
162 pub fn field_matches(&self, path: &str, pattern: &str) -> bool {
164 let Some(field_value) = self.get_string(path) else {
165 return false;
166 };
167 regex::Regex::new(pattern)
168 .map(|re| re.is_match(&field_value))
169 .unwrap_or(false)
170 }
171
172 pub fn field_is_true(&self, path: &str) -> bool {
174 self.get_bool(path).unwrap_or(false)
175 }
176
177 pub fn field_is_false(&self, path: &str) -> bool {
179 self.get_bool(path).map_or(false, |b| !b)
180 }
181
182 pub fn inner(&self) -> &JsonValue {
184 &self.0
185 }
186
187 pub fn into_inner(self) -> JsonValue {
189 self.0
190 }
191}
192
193impl Default for Value {
194 fn default() -> Self {
195 Self::null()
196 }
197}
198
199impl From<JsonValue> for Value {
200 fn from(v: JsonValue) -> Self {
201 Self(v)
202 }
203}
204
205impl From<Value> for JsonValue {
206 fn from(v: Value) -> Self {
207 v.0
208 }
209}
210
211impl From<&str> for Value {
212 fn from(s: &str) -> Self {
213 Self::string(s)
214 }
215}
216
217impl From<String> for Value {
218 fn from(s: String) -> Self {
219 Self::string(s)
220 }
221}
222
223impl From<i64> for Value {
224 fn from(v: i64) -> Self {
225 Self::int(v)
226 }
227}
228
229impl From<f64> for Value {
230 fn from(v: f64) -> Self {
231 Self::float(v)
232 }
233}
234
235impl From<bool> for Value {
236 fn from(v: bool) -> Self {
237 Self::bool(v)
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use serde_json::json;
245
246 #[test]
247 fn value_from_bytes() {
248 let bytes = br#"{"name": "test", "score": 0.95}"#;
249 let value = Value::from_bytes(bytes).unwrap();
250
251 assert_eq!(value.get_string("name"), Some("test".to_string()));
252 assert_eq!(value.get_f64("score"), Some(0.95));
253 }
254
255 #[test]
256 fn value_nested_field_access() {
257 let value = Value(json!({
258 "result": {
259 "status": "success",
260 "data": {
261 "count": 42
262 }
263 }
264 }));
265
266 assert_eq!(
267 value.get_string("result.status"),
268 Some("success".to_string())
269 );
270 assert_eq!(value.get_f64("result.data.count"), Some(42.0));
271 }
272
273 #[test]
274 fn value_jsonpath_prefix() {
275 let value = Value(json!({"score": 0.9}));
276
277 assert_eq!(value.get_f64("score"), Some(0.9));
279 assert_eq!(value.get_f64("$.score"), Some(0.9));
280 }
281
282 #[test]
283 fn value_array_access() {
284 let value = Value(json!({
285 "items": [
286 {"name": "first"},
287 {"name": "second"}
288 ]
289 }));
290
291 assert_eq!(value.get_string("items[0].name"), Some("first".to_string()));
292 assert_eq!(
293 value.get_string("items[1].name"),
294 Some("second".to_string())
295 );
296 }
297
298 #[test]
299 fn field_equals() {
300 let value = Value(json!({"status": "active"}));
301 assert!(value.field_equals("status", "active"));
302 assert!(!value.field_equals("status", "inactive"));
303 }
304
305 #[test]
306 fn field_greater_than() {
307 let value = Value(json!({"score": 0.85}));
308 assert!(value.field_greater_than("score", 0.8));
309 assert!(!value.field_greater_than("score", 0.9));
310 }
311
312 #[test]
313 fn field_less_than() {
314 let value = Value(json!({"temperature": 25.5}));
315 assert!(value.field_less_than("temperature", 30.0));
316 assert!(!value.field_less_than("temperature", 20.0));
317 }
318
319 #[test]
320 fn field_matches() {
321 let value = Value(json!({"email": "user@example.com"}));
322 assert!(value.field_matches("email", r"^[\w.+-]+@[\w.-]+\.\w+$"));
323 assert!(!value.field_matches("email", r"^invalid"));
324 }
325
326 #[test]
327 fn field_bool_checks() {
328 let value = Value(json!({"success": true, "failed": false}));
329 assert!(value.field_is_true("success"));
330 assert!(!value.field_is_true("failed"));
331 assert!(value.field_is_false("failed"));
332 assert!(!value.field_is_false("success"));
333 }
334
335 #[test]
336 fn empty_bytes_returns_null() {
337 let value = Value::from_bytes(&[]).unwrap();
338 assert!(value.is_null());
339 }
340
341 #[test]
342 fn missing_field_returns_none() {
343 let value = Value(json!({"a": 1}));
344 assert!(value.get_field("missing").is_none());
345 assert!(value.get_f64("missing").is_none());
346 }
347}