use hurl_core::ast::{Assert, SourceInfo};
use hurl_core::reader::Pos;
use crate::http;
use crate::util::path::ContextDir;
use super::cache::BodyCache;
use super::diff::diff;
use super::error::{RunnerError, RunnerErrorKind};
use super::filter::eval_filters;
use super::predicate::eval_predicate;
use super::query::eval_query;
use super::result::AssertResult;
use super::value::Value;
use super::variable::VariableSet;
impl AssertResult {
pub fn to_runner_error(&self) -> Option<RunnerError> {
match self {
AssertResult::ImplicitVersion {
actual,
expected,
source_info,
} => {
if expected.as_str() == "HTTP" || actual == expected {
None
} else {
let kind = RunnerErrorKind::AssertVersion {
actual: actual.to_string(),
};
Some(RunnerError::new(*source_info, kind, false))
}
}
AssertResult::ImplicitStatus {
actual,
expected,
source_info,
} => {
if actual == expected {
None
} else {
let kind = RunnerErrorKind::AssertStatus {
actual: actual.to_string(),
};
Some(RunnerError::new(*source_info, kind, false))
}
}
AssertResult::ImplicitHeader {
actual,
expected,
source_info,
} => match actual {
Err(e) => Some(e.clone()),
Ok(s) => {
if s == expected {
None
} else {
let kind = RunnerErrorKind::AssertHeaderValueError { actual: s.clone() };
Some(RunnerError::new(*source_info, kind, false))
}
}
},
AssertResult::ImplicitBody {
actual,
expected,
source_info,
} => match expected {
Err(e) => Some(e.clone()),
Ok(expected) => match actual {
Err(e) => Some(e.clone()),
Ok(actual) => {
if actual == expected {
None
} else if use_diff(expected, actual) {
let actual = actual.to_string();
let expected = expected.to_string();
let hunks = diff(&expected, &actual);
let source_line = hunks
.clone()
.first()
.expect("at least a diff hunk")
.source_line;
let kind = RunnerErrorKind::AssertBodyDiffError {
hunks,
body_source_info: *source_info,
};
let diff_source_info = SourceInfo::new(
Pos::new(source_info.start.line + source_line, 1),
Pos::new(source_info.start.line + source_line, 1),
);
Some(RunnerError::new(diff_source_info, kind, false))
} else {
let actual = actual.to_string();
let expected = expected.to_string();
let kind = RunnerErrorKind::AssertBodyValueError { actual, expected };
Some(RunnerError::new(*source_info, kind, false))
}
}
},
},
AssertResult::Explicit { actual: Err(e), .. } => Some(e.clone()),
AssertResult::Explicit {
predicate_result: Some(Err(e)),
..
} => Some(e.clone()),
_ => None,
}
}
pub fn line(&self) -> usize {
match self {
AssertResult::ImplicitVersion { source_info, .. } => source_info.start.line,
AssertResult::ImplicitStatus { source_info, .. } => source_info.start.line,
AssertResult::ImplicitHeader { source_info, .. } => source_info.start.line,
AssertResult::ImplicitBody { source_info, .. } => source_info.start.line,
AssertResult::Explicit { source_info, .. } => source_info.start.line,
}
}
}
fn use_diff(expected: &Value, actual: &Value) -> bool {
if let (Value::String(expected), Value::String(actual)) = (actual, expected) {
expected.contains('\n') || actual.contains('\n')
} else {
false
}
}
pub fn eval_explicit_assert(
assert: &Assert,
variables: &VariableSet,
http_responses: &[&http::Response],
cache: &mut BodyCache,
context_dir: &ContextDir,
) -> AssertResult {
let query_result = eval_query(&assert.query, variables, http_responses, cache);
let actual = if assert.filters.is_empty() {
query_result
} else if let Ok(optional_value) = query_result {
match optional_value {
None => Err(RunnerError {
source_info: assert
.filters
.first()
.expect("at least one filter")
.1
.source_info,
kind: RunnerErrorKind::FilterMissingInput,
assert: true,
}),
Some(value) => {
let filters = assert.filters.iter().map(|(_, f)| f).collect::<Vec<_>>();
match eval_filters(&filters, &value, variables, true) {
Ok(value) => Ok(value),
Err(e) => Err(e),
}
}
}
} else {
query_result
};
let source_info = assert.predicate.predicate_func.source_info;
let predicate_result = match &actual {
Err(_) => None,
Ok(actual) => Some(eval_predicate(
&assert.predicate,
variables,
actual,
context_dir,
)),
};
AssertResult::Explicit {
actual,
source_info,
predicate_result,
}
}
#[cfg(test)]
pub mod tests {
use std::path::Path;
use hurl_core::ast::{
Filter, FilterValue, I64, LineTerminator, Predicate, PredicateFunc, PredicateFuncValue,
PredicateValue, SourceInfo, Whitespace,
};
use hurl_core::reader::Pos;
use hurl_core::types::ToSource;
use super::super::query;
use super::*;
use crate::http::xml_three_users_http_response;
use crate::runner::Number;
pub fn assert_count_user() -> Assert {
let whitespace = Whitespace {
value: String::from(" "),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
};
let predicate = Predicate {
not: false,
space0: whitespace.clone(),
predicate_func: PredicateFunc {
source_info: SourceInfo::new(Pos::new(1, 22), Pos::new(1, 24)),
value: PredicateFuncValue::Equal {
space0: whitespace.clone(),
value: PredicateValue::Number(hurl_core::ast::Number::Integer(I64::new(
3,
"3".to_source(),
))),
},
},
};
Assert {
line_terminators: vec![],
space0: whitespace.clone(),
query: query::tests::xpath_users(),
filters: vec![(
whitespace.clone(),
Filter {
source_info: SourceInfo::new(Pos::new(1, 16), Pos::new(1, 21)),
value: FilterValue::Count,
},
)],
space1: whitespace.clone(),
predicate,
line_terminator0: LineTerminator {
space0: whitespace.clone(),
comment: None,
newline: whitespace,
},
}
}
#[test]
fn test_invalid_xpath() {}
#[test]
fn test_eval() {
let variables = VariableSet::new();
let current_dir = Path::new("/home");
let file_root = Path::new("file_root");
let context_dir = ContextDir::new(current_dir, file_root);
let mut cache = BodyCache::new();
assert_eq!(
eval_explicit_assert(
&assert_count_user(),
&variables,
&[&xml_three_users_http_response()],
&mut cache,
&context_dir
),
AssertResult::Explicit {
actual: Ok(Some(Value::Number(Number::Integer(3)))),
source_info: SourceInfo::new(Pos::new(1, 22), Pos::new(1, 24)),
predicate_result: Some(Ok(())),
}
);
}
#[test]
pub fn test_use_diff() {
assert!(!use_diff(&Value::Bool(true), &Value::Bool(false)));
assert!(!use_diff(
&Value::String("a".to_string()),
&Value::String("b".to_string())
));
assert!(use_diff(
&Value::String("a\n".to_string()),
&Value::String("b".to_string())
));
}
}