use clove_lang::{Evaluator, Lexer, Parser};
use regex::Regex;
use super::spec::{Assertion, AssertionValue};
use super::result::AssertionResult;
use super::RunnerError;
pub struct AssertionEvaluator {
current_response: Option<serde_json::Value>,
previous_response: Option<serde_json::Value>,
}
impl AssertionEvaluator {
pub fn new() -> Self {
Self {
current_response: None,
previous_response: None,
}
}
pub fn set_current(&mut self, response: serde_json::Value) {
self.previous_response = self.current_response.take();
self.current_response = Some(response);
}
pub fn has_previous(&self) -> bool {
self.previous_response.is_some()
}
pub fn evaluate(&self, assertion: &Assertion) -> AssertionResult {
let Some(ref current) = self.current_response else {
return AssertionResult::error("No current response to evaluate");
};
let Some(ref query) = assertion.query else {
return AssertionResult::error("Assertion has no query");
};
let actual = match self.evaluate_query(query, current) {
Ok(v) => v,
Err(e) => return AssertionResult::error(&format!("Query evaluation failed: {}", e)),
};
if let Some(ref expected) = assertion.expect {
return self.check_exact_match(&actual, expected, assertion);
}
if let Some(ref expected) = assertion.expect_gt {
return self.check_comparison(&actual, expected, ">", assertion);
}
if let Some(ref expected) = assertion.expect_lt {
return self.check_comparison(&actual, expected, "<", assertion);
}
if let Some(ref expected) = assertion.expect_gte {
return self.check_comparison(&actual, expected, ">=", assertion);
}
if let Some(ref expected) = assertion.expect_lte {
return self.check_comparison(&actual, expected, "<=", assertion);
}
if let Some(ref expected_type) = assertion.expect_type {
return self.check_type(&actual, expected_type, assertion);
}
if let Some(ref pattern) = assertion.expect_match {
return self.check_regex(&actual, pattern, assertion);
}
AssertionResult::error("Assertion has no expectation specified")
}
fn evaluate_query(&self, query: &str, data: &serde_json::Value) -> Result<serde_json::Value, RunnerError> {
if query.contains("@prev") {
return self.evaluate_with_prev(query, data);
}
let lexer = Lexer::new(query);
let mut parser = Parser::new(lexer)
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
let parsed = parser.parse()
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
let clove_data = clove_lang::json_to_clove(data.clone());
let result = Evaluator::new()
.eval_expression(&parsed, clove_data)
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
Ok(clove_lang::clove_to_json(result))
}
fn evaluate_with_prev(&self, query: &str, current_data: &serde_json::Value) -> Result<serde_json::Value, RunnerError> {
let Some(ref prev_data) = self.previous_response else {
return Err(RunnerError::CloveError("@prev referenced but no previous response available".to_string()));
};
if query.starts_with("@prev") {
let adjusted_query = query.replacen("@prev", "$", 1);
let lexer = Lexer::new(&adjusted_query);
let mut parser = Parser::new(lexer)
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
let parsed = parser.parse()
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
let clove_data = clove_lang::json_to_clove(prev_data.clone());
let result = Evaluator::new()
.eval_expression(&parsed, clove_data)
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
return Ok(clove_lang::clove_to_json(result));
}
let lexer = Lexer::new(query);
let mut parser = Parser::new(lexer)
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
let parsed = parser.parse()
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
let clove_data = clove_lang::json_to_clove(current_data.clone());
let result = Evaluator::new()
.eval_expression(&parsed, clove_data)
.map_err(|e| RunnerError::CloveError(format!("{}", e)))?;
Ok(clove_lang::clove_to_json(result))
}
fn check_exact_match(&self, actual: &serde_json::Value, expected: &serde_json::Value, assertion: &Assertion) -> AssertionResult {
if actual == expected {
AssertionResult::passed()
} else {
AssertionResult::failed(
assertion.message.as_deref().unwrap_or("Value mismatch"),
format!("{}", expected),
format!("{}", actual),
)
}
}
fn check_comparison(&self, actual: &serde_json::Value, expected: &AssertionValue, op: &str, assertion: &Assertion) -> AssertionResult {
let expected_val = match self.resolve_assertion_value(expected) {
Ok(v) => v,
Err(e) => return AssertionResult::error(&e.to_string()),
};
let actual_num = match self.to_number(actual) {
Some(n) => n,
None => return AssertionResult::error(&format!("Cannot compare non-numeric value: {}", actual)),
};
let expected_num = match self.to_number(&expected_val) {
Some(n) => n,
None => return AssertionResult::error(&format!("Cannot compare to non-numeric value: {}", expected_val)),
};
let passed = match op {
">" => actual_num > expected_num,
"<" => actual_num < expected_num,
">=" => actual_num >= expected_num,
"<=" => actual_num <= expected_num,
_ => return AssertionResult::error(&format!("Unknown comparison operator: {}", op)),
};
if passed {
AssertionResult::passed()
} else {
AssertionResult::failed(
assertion.message.as_deref().unwrap_or(&format!("Expected {} {}", op, expected_num)),
format!("{} {}", op, expected_num),
format!("{}", actual_num),
)
}
}
fn check_type(&self, actual: &serde_json::Value, expected_type: &str, assertion: &Assertion) -> AssertionResult {
let actual_type = match actual {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "boolean",
serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => "integer",
serde_json::Value::Number(_) => "number",
serde_json::Value::String(_) => "string",
serde_json::Value::Array(_) => "array",
serde_json::Value::Object(_) => "object",
};
let matches = actual_type == expected_type
|| (expected_type == "number" && actual_type == "integer");
if matches {
AssertionResult::passed()
} else {
AssertionResult::failed(
assertion.message.as_deref().unwrap_or("Type mismatch"),
expected_type.to_string(),
actual_type.to_string(),
)
}
}
fn check_regex(&self, actual: &serde_json::Value, pattern: &str, assertion: &Assertion) -> AssertionResult {
let actual_str = match actual {
serde_json::Value::String(s) => s.as_str(),
_ => return AssertionResult::error("Cannot apply regex to non-string value"),
};
let re = match Regex::new(pattern) {
Ok(r) => r,
Err(e) => return AssertionResult::error(&format!("Invalid regex: {}", e)),
};
if re.is_match(actual_str) {
AssertionResult::passed()
} else {
AssertionResult::failed(
assertion.message.as_deref().unwrap_or("Pattern did not match"),
format!("matches /{}/", pattern),
actual_str.to_string(),
)
}
}
fn resolve_assertion_value(&self, value: &AssertionValue) -> Result<serde_json::Value, RunnerError> {
match value {
AssertionValue::Number(n) => Ok(serde_json::json!(n)),
AssertionValue::Integer(n) => Ok(serde_json::json!(n)),
AssertionValue::String(s) => {
if s.starts_with('$') || s.starts_with('@') {
let data = if s.starts_with("@prev") {
self.previous_response.as_ref()
.ok_or_else(|| RunnerError::CloveError("@prev referenced but no previous response".to_string()))?
} else {
self.current_response.as_ref()
.ok_or_else(|| RunnerError::CloveError("No current response".to_string()))?
};
let query = if s.starts_with("@prev") {
s.replacen("@prev", "$", 1)
} else {
s.clone()
};
self.evaluate_query(&query, data)
} else {
Ok(serde_json::json!(s))
}
}
}
}
fn to_number(&self, value: &serde_json::Value) -> Option<f64> {
match value {
serde_json::Value::Number(n) => n.as_f64(),
_ => None,
}
}
}
impl Default for AssertionEvaluator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exact_match() {
let mut eval = AssertionEvaluator::new();
eval.set_current(serde_json::json!({"status": "ok"}));
let assertion = Assertion {
query: Some("$[status]".to_string()),
expect: Some(serde_json::json!("ok")),
..Default::default()
};
let result = eval.evaluate(&assertion);
assert!(result.passed);
}
#[test]
fn test_type_check() {
let mut eval = AssertionEvaluator::new();
eval.set_current(serde_json::json!({"count": 42}));
let assertion = Assertion {
query: Some("$[count]".to_string()),
expect_type: Some("integer".to_string()),
..Default::default()
};
let result = eval.evaluate(&assertion);
assert!(result.passed);
}
#[test]
fn test_comparison_gt() {
let mut eval = AssertionEvaluator::new();
eval.set_current(serde_json::json!({"count": 10}));
let assertion = Assertion {
query: Some("$[count]".to_string()),
expect_gt: Some(AssertionValue::Integer(5)),
..Default::default()
};
let result = eval.evaluate(&assertion);
assert!(result.passed);
}
#[test]
fn test_prev_comparison() {
let mut eval = AssertionEvaluator::new();
eval.set_current(serde_json::json!({"count": 5}));
eval.set_current(serde_json::json!({"count": 10}));
let assertion = Assertion {
query: Some("$[count]".to_string()),
expect_gt: Some(AssertionValue::String("@prev[count]".to_string())),
..Default::default()
};
let result = eval.evaluate(&assertion);
assert!(result.passed, "Expected 10 > 5, but got: {:?}", result);
}
}
impl Default for Assertion {
fn default() -> Self {
Self {
query: None,
expect: None,
expect_gt: None,
expect_lt: None,
expect_gte: None,
expect_lte: None,
expect_type: None,
expect_match: None,
message: None,
scope: None,
}
}
}