Skip to main content

jpx_engine/
types.rs

1//! Common types for engine requests and responses.
2//!
3//! These types are used for structured input/output, particularly useful
4//! when building APIs or serializing results.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// Request to evaluate a JMESPath expression.
10///
11/// This struct packages an expression with its input data, useful for
12/// API endpoints or batch processing.
13///
14/// # Example
15///
16/// ```rust
17/// use jpx_engine::EvalRequest;
18/// use serde_json::json;
19///
20/// let request = EvalRequest {
21///     expression: "users[*].name".to_string(),
22///     input: json!({"users": [{"name": "alice"}]}),
23/// };
24/// ```
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct EvalRequest {
27    /// The JMESPath expression to evaluate
28    pub expression: String,
29    /// The JSON input to evaluate against
30    pub input: Value,
31}
32
33/// Response from evaluating a JMESPath expression.
34///
35/// Wraps the evaluation result in a structured response.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct EvalResponse {
38    /// The result of evaluation
39    pub result: Value,
40}
41
42/// Result of validating a JMESPath expression.
43///
44/// Returned by [`JpxEngine::validate`](crate::JpxEngine::validate) to indicate
45/// whether an expression has valid syntax.
46///
47/// # Example
48///
49/// ```rust
50/// use jpx_engine::JpxEngine;
51///
52/// let engine = JpxEngine::new();
53///
54/// let result = engine.validate("users[*].name");
55/// assert!(result.valid);
56/// assert!(result.error.is_none());
57///
58/// let result = engine.validate("users[*.name");  // missing bracket
59/// assert!(!result.valid);
60/// assert!(result.error.is_some());
61/// ```
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ValidationResult {
64    /// `true` if the expression has valid syntax
65    pub valid: bool,
66    /// Error message if validation failed, `None` if valid
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub error: Option<String>,
69}
70
71/// Result for a single expression in batch evaluation.
72///
73/// Each expression in a batch produces one of these, containing either
74/// a successful result or an error message.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct BatchExpressionResult {
77    /// The expression that was evaluated
78    pub expression: String,
79    /// The result if evaluation succeeded
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub result: Option<Value>,
82    /// Error message if evaluation failed
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub error: Option<String>,
85}
86
87/// Result of batch evaluation.
88///
89/// Contains results for all expressions evaluated in
90/// [`JpxEngine::batch_evaluate`](crate::JpxEngine::batch_evaluate).
91/// Results are in the same order as the input expressions.
92///
93/// # Example
94///
95/// ```rust
96/// use jpx_engine::JpxEngine;
97/// use serde_json::json;
98///
99/// let engine = JpxEngine::new();
100/// let input = json!({"a": 1, "b": 2});
101/// let exprs = vec!["a".to_string(), "b".to_string(), "c".to_string()];
102///
103/// let batch = engine.batch_evaluate(&exprs, &input);
104///
105/// // Results are in order
106/// assert_eq!(batch.results.len(), 3);
107/// assert_eq!(batch.results[0].expression, "a");
108/// assert_eq!(batch.results[0].result, Some(json!(1)));
109/// ```
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct BatchEvaluateResult {
112    /// Results for each expression, in order
113    pub results: Vec<BatchExpressionResult>,
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use serde_json::json;
120
121    #[test]
122    fn test_eval_request_serde_roundtrip() {
123        let request = EvalRequest {
124            expression: "users[*].name".to_string(),
125            input: json!({"users": [{"name": "alice"}]}),
126        };
127
128        let json_str = serde_json::to_string(&request).unwrap();
129        let deserialized: EvalRequest = serde_json::from_str(&json_str).unwrap();
130
131        assert_eq!(deserialized.expression, "users[*].name");
132        assert_eq!(deserialized.input, json!({"users": [{"name": "alice"}]}));
133    }
134
135    #[test]
136    fn test_eval_response_serde_roundtrip() {
137        let response = EvalResponse {
138            result: json!(["alice", "bob"]),
139        };
140
141        let json_str = serde_json::to_string(&response).unwrap();
142        let deserialized: EvalResponse = serde_json::from_str(&json_str).unwrap();
143
144        assert_eq!(deserialized.result, json!(["alice", "bob"]));
145    }
146
147    #[test]
148    fn test_validation_result_valid_roundtrip() {
149        let result = ValidationResult {
150            valid: true,
151            error: None,
152        };
153
154        let json_str = serde_json::to_string(&result).unwrap();
155        let deserialized: ValidationResult = serde_json::from_str(&json_str).unwrap();
156
157        assert!(deserialized.valid);
158        assert!(deserialized.error.is_none());
159    }
160
161    #[test]
162    fn test_validation_result_invalid_roundtrip() {
163        let result = ValidationResult {
164            valid: false,
165            error: Some("unexpected token".to_string()),
166        };
167
168        let json_str = serde_json::to_string(&result).unwrap();
169        let deserialized: ValidationResult = serde_json::from_str(&json_str).unwrap();
170
171        assert!(!deserialized.valid);
172        assert_eq!(deserialized.error.as_deref(), Some("unexpected token"));
173    }
174
175    #[test]
176    fn test_validation_result_skip_none_error() {
177        let result = ValidationResult {
178            valid: true,
179            error: None,
180        };
181
182        let json_str = serde_json::to_string(&result).unwrap();
183        let json_value: Value = serde_json::from_str(&json_str).unwrap();
184
185        assert!(json_value.get("error").is_none());
186    }
187
188    #[test]
189    fn test_batch_expression_result_success() {
190        let result = BatchExpressionResult {
191            expression: "a.b".to_string(),
192            result: Some(json!(42)),
193            error: None,
194        };
195
196        let json_str = serde_json::to_string(&result).unwrap();
197        let deserialized: BatchExpressionResult = serde_json::from_str(&json_str).unwrap();
198
199        assert_eq!(deserialized.expression, "a.b");
200        assert_eq!(deserialized.result, Some(json!(42)));
201        assert!(deserialized.error.is_none());
202    }
203
204    #[test]
205    fn test_batch_expression_result_error() {
206        let result = BatchExpressionResult {
207            expression: "invalid[".to_string(),
208            result: None,
209            error: Some("parse error".to_string()),
210        };
211
212        let json_str = serde_json::to_string(&result).unwrap();
213        let deserialized: BatchExpressionResult = serde_json::from_str(&json_str).unwrap();
214
215        assert_eq!(deserialized.expression, "invalid[");
216        assert!(deserialized.result.is_none());
217        assert_eq!(deserialized.error.as_deref(), Some("parse error"));
218    }
219
220    #[test]
221    fn test_batch_expression_result_skip_none_fields() {
222        let result = BatchExpressionResult {
223            expression: "a".to_string(),
224            result: Some(json!(1)),
225            error: None,
226        };
227
228        let json_str = serde_json::to_string(&result).unwrap();
229        let json_value: Value = serde_json::from_str(&json_str).unwrap();
230
231        assert!(json_value.get("expression").is_some());
232        assert!(json_value.get("result").is_some());
233        assert!(json_value.get("error").is_none());
234
235        let error_result = BatchExpressionResult {
236            expression: "bad".to_string(),
237            result: None,
238            error: Some("fail".to_string()),
239        };
240
241        let json_str = serde_json::to_string(&error_result).unwrap();
242        let json_value: Value = serde_json::from_str(&json_str).unwrap();
243
244        assert!(json_value.get("expression").is_some());
245        assert!(json_value.get("result").is_none());
246        assert!(json_value.get("error").is_some());
247    }
248
249    #[test]
250    fn test_batch_evaluate_result_roundtrip() {
251        let batch = BatchEvaluateResult {
252            results: vec![
253                BatchExpressionResult {
254                    expression: "a".to_string(),
255                    result: Some(json!(1)),
256                    error: None,
257                },
258                BatchExpressionResult {
259                    expression: "b".to_string(),
260                    result: Some(json!(2)),
261                    error: None,
262                },
263                BatchExpressionResult {
264                    expression: "invalid[".to_string(),
265                    result: None,
266                    error: Some("parse error".to_string()),
267                },
268            ],
269        };
270
271        let json_str = serde_json::to_string(&batch).unwrap();
272        let deserialized: BatchEvaluateResult = serde_json::from_str(&json_str).unwrap();
273
274        assert_eq!(deserialized.results.len(), 3);
275        assert_eq!(deserialized.results[0].expression, "a");
276        assert_eq!(deserialized.results[0].result, Some(json!(1)));
277        assert_eq!(deserialized.results[1].expression, "b");
278        assert_eq!(deserialized.results[1].result, Some(json!(2)));
279        assert_eq!(deserialized.results[2].expression, "invalid[");
280        assert!(deserialized.results[2].result.is_none());
281        assert_eq!(
282            deserialized.results[2].error.as_deref(),
283            Some("parse error")
284        );
285    }
286
287    #[test]
288    fn test_eval_request_complex_input() {
289        let request = EvalRequest {
290            expression: "data.users[?age > `30`].name".to_string(),
291            input: json!({
292                "data": {
293                    "users": [
294                        {"name": "alice", "age": 25, "tags": ["admin", "user"]},
295                        {"name": "bob", "age": 35, "tags": ["user"]},
296                        {"name": "carol", "age": 40, "tags": []}
297                    ],
298                    "metadata": {
299                        "count": 3,
300                        "active": true,
301                        "ratio": 0.75,
302                        "nothing": null
303                    }
304                }
305            }),
306        };
307
308        let json_str = serde_json::to_string(&request).unwrap();
309        let deserialized: EvalRequest = serde_json::from_str(&json_str).unwrap();
310
311        assert_eq!(deserialized.expression, "data.users[?age > `30`].name");
312        assert_eq!(deserialized.input, request.input);
313        assert_eq!(deserialized.input["data"]["users"][0]["name"], "alice");
314        assert_eq!(deserialized.input["data"]["metadata"]["count"], 3);
315        assert_eq!(deserialized.input["data"]["metadata"]["active"], true);
316        assert_eq!(deserialized.input["data"]["metadata"]["ratio"], 0.75);
317        assert!(deserialized.input["data"]["metadata"]["nothing"].is_null());
318    }
319}