Implement functions with Value
This commit is contained in:
parent
7265f3007f
commit
583cf901bc
|
|
@ -28,7 +28,7 @@ pub use self::number::Number;
|
|||
pub use self::output::Output;
|
||||
pub use self::result::{AssertResult, CaptureResult, EntryResult, HurlResult};
|
||||
pub use self::runner_options::{RunnerOptions, RunnerOptionsBuilder};
|
||||
pub use self::value::Value;
|
||||
pub use self::value::{EvalError, Value};
|
||||
pub use self::variable::{Variable, VariableSet, Visibility};
|
||||
|
||||
mod assert;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ use std::cmp::Ordering;
|
|||
use crate::runner::error::RunnerError;
|
||||
use crate::runner::predicate_value::{eval_predicate_value, eval_predicate_value_template};
|
||||
use crate::runner::result::PredicateResult;
|
||||
use crate::runner::template::eval_template;
|
||||
use crate::runner::value::Value;
|
||||
use crate::runner::value::{EvalError, Value};
|
||||
use crate::runner::{Number, RunnerErrorKind, VariableSet};
|
||||
use crate::util::path::ContextDir;
|
||||
|
||||
|
|
@ -260,7 +259,13 @@ fn eval_predicate_func(
|
|||
} => eval_include(expected, variables, value, context_dir),
|
||||
PredicateFuncValue::Match {
|
||||
value: expected, ..
|
||||
} => eval_match(expected, predicate_func.source_info, variables, value),
|
||||
} => eval_match(
|
||||
expected,
|
||||
predicate_func.source_info,
|
||||
variables,
|
||||
value,
|
||||
context_dir,
|
||||
),
|
||||
PredicateFuncValue::IsInteger => eval_is_integer(value),
|
||||
PredicateFuncValue::IsFloat => eval_is_float(value),
|
||||
PredicateFuncValue::IsBoolean => eval_is_boolean(value),
|
||||
|
|
@ -351,20 +356,14 @@ fn eval_start_with(
|
|||
let expected = eval_predicate_value(expected, variables, context_dir)?;
|
||||
let expected_display = format!("starts with {}", expected.repr());
|
||||
let actual_display = actual.repr();
|
||||
match (expected, actual) {
|
||||
(Value::String(expected), Value::String(actual)) => Ok(AssertResult {
|
||||
success: actual.as_str().starts_with(expected.as_str()),
|
||||
match actual.starts_with(&expected) {
|
||||
Ok(success) => Ok(AssertResult {
|
||||
success,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
(Value::Bytes(expected), Value::Bytes(actual)) => Ok(AssertResult {
|
||||
success: actual.starts_with(&expected),
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
_ => Ok(AssertResult {
|
||||
Err(_) => Ok(AssertResult {
|
||||
success: false,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
|
|
@ -384,20 +383,14 @@ fn eval_end_with(
|
|||
let expected = eval_predicate_value(expected, variables, context_dir)?;
|
||||
let expected_display = format!("ends with {}", expected.repr());
|
||||
let actual_display = actual.repr();
|
||||
match (expected, actual) {
|
||||
(Value::String(expected), Value::String(actual)) => Ok(AssertResult {
|
||||
success: actual.as_str().ends_with(expected.as_str()),
|
||||
match actual.ends_with(&expected) {
|
||||
Ok(success) => Ok(AssertResult {
|
||||
success,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
(Value::Bytes(expected), Value::Bytes(actual)) => Ok(AssertResult {
|
||||
success: actual.ends_with(&expected),
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
_ => Ok(AssertResult {
|
||||
Err(_) => Ok(AssertResult {
|
||||
success: false,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
|
|
@ -417,15 +410,9 @@ fn eval_contain(
|
|||
let expected = eval_predicate_value(expected, variables, context_dir)?;
|
||||
let expected_display = format!("contains {}", expected.repr());
|
||||
let actual_display = actual.repr();
|
||||
match (expected, actual) {
|
||||
(Value::String(expected), Value::String(actual)) => Ok(AssertResult {
|
||||
success: actual.as_str().contains(expected.as_str()),
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
(Value::Bytes(expected), Value::Bytes(actual)) => Ok(AssertResult {
|
||||
success: contains(actual.as_slice(), expected.as_slice()),
|
||||
match actual.contains(&expected) {
|
||||
Ok(success) => Ok(AssertResult {
|
||||
success,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
|
|
@ -457,47 +444,36 @@ fn eval_match(
|
|||
source_info: SourceInfo,
|
||||
variables: &VariableSet,
|
||||
actual: &Value,
|
||||
context_dir: &ContextDir,
|
||||
) -> Result<AssertResult, RunnerError> {
|
||||
let regex = match expected {
|
||||
PredicateValue::String(template) => {
|
||||
let expected = eval_template(template, variables)?;
|
||||
match regex::Regex::new(expected.as_str()) {
|
||||
Ok(re) => re,
|
||||
Err(_) => {
|
||||
return Err(RunnerError::new(
|
||||
source_info,
|
||||
RunnerErrorKind::InvalidRegex,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
PredicateValue::Regex(regex) => regex.inner.clone(),
|
||||
_ => panic!("expect a string predicate value"), // should have failed in parsing
|
||||
};
|
||||
let expected = eval_predicate_value(expected, variables, context_dir)?;
|
||||
let actual_display = actual.repr();
|
||||
let expected_display = format!("matches regex <{regex}>");
|
||||
match actual {
|
||||
Value::String(value) => Ok(AssertResult {
|
||||
success: regex.is_match(value.as_str()),
|
||||
let expected_display = format!("matches regex <{expected}>");
|
||||
match actual.is_match(&expected) {
|
||||
Ok(success) => Ok(AssertResult {
|
||||
success,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
_ => Ok(AssertResult {
|
||||
Err(EvalError::Type) => Ok(AssertResult {
|
||||
success: false,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: true,
|
||||
}),
|
||||
Err(EvalError::InvalidRegex) => Err(RunnerError::new(
|
||||
source_info,
|
||||
RunnerErrorKind::InvalidRegex,
|
||||
false,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates if an `actual` value is an integer.
|
||||
fn eval_is_integer(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
Ok(AssertResult {
|
||||
success: matches!(actual, Value::Number(Number::Integer(_)))
|
||||
|| matches!(actual, Value::Number(Number::BigInteger(_))),
|
||||
success: actual.is_integer(),
|
||||
actual: actual.repr(),
|
||||
expected: "integer".to_string(),
|
||||
type_mismatch: false,
|
||||
|
|
@ -507,7 +483,7 @@ fn eval_is_integer(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// Evaluates if an `actual` value is a float.
|
||||
fn eval_is_float(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
Ok(AssertResult {
|
||||
success: matches!(actual, Value::Number(Number::Float(_))),
|
||||
success: actual.is_float(),
|
||||
actual: actual.repr(),
|
||||
expected: "float".to_string(),
|
||||
type_mismatch: false,
|
||||
|
|
@ -517,7 +493,7 @@ fn eval_is_float(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// Evaluates if an `actual` value is a boolean.
|
||||
fn eval_is_boolean(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
Ok(AssertResult {
|
||||
success: matches!(actual, Value::Bool(_)),
|
||||
success: actual.is_boolean(),
|
||||
actual: actual.repr(),
|
||||
expected: "boolean".to_string(),
|
||||
type_mismatch: false,
|
||||
|
|
@ -527,7 +503,7 @@ fn eval_is_boolean(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// Evaluates if an `actual` value is a string.
|
||||
fn eval_is_string(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
Ok(AssertResult {
|
||||
success: matches!(actual, Value::String(_)),
|
||||
success: actual.is_string(),
|
||||
actual: actual.repr(),
|
||||
expected: "string".to_string(),
|
||||
type_mismatch: false,
|
||||
|
|
@ -537,10 +513,7 @@ fn eval_is_string(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// Evaluates if an `actual` value is a collection.
|
||||
fn eval_is_collection(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
Ok(AssertResult {
|
||||
success: matches!(actual, Value::Bytes(_))
|
||||
|| matches!(actual, Value::List(_))
|
||||
|| matches!(actual, Value::Nodeset(_))
|
||||
|| matches!(actual, Value::Object(_)),
|
||||
success: actual.is_collection(),
|
||||
actual: actual.repr(),
|
||||
expected: "collection".to_string(),
|
||||
type_mismatch: false,
|
||||
|
|
@ -550,7 +523,7 @@ fn eval_is_collection(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// Evaluates if an `actual` value is a date.
|
||||
fn eval_is_date(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
Ok(AssertResult {
|
||||
success: matches!(actual, Value::Date(_)),
|
||||
success: actual.is_date(),
|
||||
actual: actual.repr(),
|
||||
expected: "date".to_string(),
|
||||
type_mismatch: false,
|
||||
|
|
@ -562,10 +535,10 @@ fn eval_is_date(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// [`eval_is_date`] performs type check (is the input of [`Value::Date`]), whereas [`eval_is_iso_date`]
|
||||
/// checks if a string conforms to a certain date-time format.
|
||||
fn eval_is_iso_date(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
match actual {
|
||||
Value::String(actual) => Ok(AssertResult {
|
||||
success: chrono::DateTime::parse_from_rfc3339(actual).is_ok(),
|
||||
actual: actual.clone(),
|
||||
match actual.is_iso_date() {
|
||||
Ok(success) => Ok(AssertResult {
|
||||
success,
|
||||
actual: actual.to_string(),
|
||||
expected: "string with format YYYY-MM-DDTHH:mm:ss.sssZ".to_string(),
|
||||
type_mismatch: false,
|
||||
}),
|
||||
|
|
@ -601,37 +574,16 @@ fn eval_exist(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// Evaluates if an `actual` is empty.
|
||||
fn eval_is_empty(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
let expected_display = "count equals to 0".to_string();
|
||||
match actual {
|
||||
Value::List(values) => Ok(AssertResult {
|
||||
success: values.is_empty(),
|
||||
actual: format!("count equals to {}", values.len()),
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
Value::String(data) => Ok(AssertResult {
|
||||
success: data.is_empty(),
|
||||
actual: format!("count equals to {}", data.len()),
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
Value::Nodeset(count) => Ok(AssertResult {
|
||||
success: *count == 0,
|
||||
actual: format!("count equals to {count}"),
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
Value::Object(props) => Ok(AssertResult {
|
||||
success: props.is_empty(),
|
||||
actual: format!("count equals to {}", props.len()),
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
Value::Bytes(data) => Ok(AssertResult {
|
||||
success: data.is_empty(),
|
||||
actual: format!("count equals to {}", data.len()),
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
}),
|
||||
match actual.count() {
|
||||
Ok(count) => {
|
||||
let actual_display = format!("count equals to {count}");
|
||||
Ok(AssertResult {
|
||||
success: count == 0,
|
||||
actual: actual_display,
|
||||
expected: expected_display,
|
||||
type_mismatch: false,
|
||||
})
|
||||
}
|
||||
_ => Ok(AssertResult {
|
||||
success: false,
|
||||
actual: actual.repr(),
|
||||
|
|
@ -644,7 +596,7 @@ fn eval_is_empty(actual: &Value) -> Result<AssertResult, RunnerError> {
|
|||
/// Evaluates if an `actual` value is a number.
|
||||
fn eval_is_number(actual: &Value) -> Result<AssertResult, RunnerError> {
|
||||
Ok(AssertResult {
|
||||
success: matches!(actual, Value::Number(_)),
|
||||
success: actual.is_number(),
|
||||
actual: actual.repr(),
|
||||
expected: "number".to_string(),
|
||||
type_mismatch: false,
|
||||
|
|
@ -755,39 +707,24 @@ fn assert_values_less_or_equal(actual_value: &Value, expected_value: &Value) ->
|
|||
}
|
||||
|
||||
fn assert_include(value: &Value, element: &Value) -> AssertResult {
|
||||
let actual = value.repr();
|
||||
let expected = format!("includes {}", element.repr());
|
||||
match value {
|
||||
Value::List(values) => {
|
||||
let mut success = false;
|
||||
for v in values {
|
||||
let result = assert_values_equal(v, element);
|
||||
if result.success {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
AssertResult {
|
||||
success,
|
||||
actual: value.repr(),
|
||||
expected,
|
||||
type_mismatch: false,
|
||||
}
|
||||
}
|
||||
_ => AssertResult {
|
||||
match value.includes(element) {
|
||||
Ok(success) => AssertResult {
|
||||
success,
|
||||
actual,
|
||||
expected,
|
||||
type_mismatch: false,
|
||||
},
|
||||
Err(_) => AssertResult {
|
||||
success: false,
|
||||
actual: value.repr(),
|
||||
actual,
|
||||
expected,
|
||||
type_mismatch: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
|
||||
haystack
|
||||
.windows(needle.len())
|
||||
.any(|window| window == needle)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AssertResult, *};
|
||||
|
|
@ -804,14 +741,6 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains() {
|
||||
let haystack = [1, 2, 3];
|
||||
assert!(contains(&haystack, &[1]));
|
||||
assert!(contains(&haystack, &[1, 2]));
|
||||
assert!(!contains(&haystack, &[1, 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_predicate() {
|
||||
// `not == 10` with value `1` OK
|
||||
|
|
@ -1441,6 +1370,9 @@ mod tests {
|
|||
#[test]
|
||||
fn test_predicate_match() {
|
||||
let variables = VariableSet::new();
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let file_root = Path::new("file_root");
|
||||
let context_dir = ContextDir::new(current_dir.as_path(), file_root);
|
||||
|
||||
// predicate: `matches /a{3}/`
|
||||
// value: aa
|
||||
|
|
@ -1449,11 +1381,12 @@ mod tests {
|
|||
});
|
||||
let value = Value::String("aa".to_string());
|
||||
let source_info = SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0));
|
||||
let assert_result = eval_match(&expected, source_info, &variables, &value).unwrap();
|
||||
let assert_result =
|
||||
eval_match(&expected, source_info, &variables, &value, &context_dir).unwrap();
|
||||
assert!(!assert_result.success);
|
||||
assert!(!assert_result.type_mismatch);
|
||||
assert_eq!(assert_result.actual, "string <aa>");
|
||||
assert_eq!(assert_result.expected, "matches regex <a{3}>");
|
||||
assert_eq!(assert_result.expected, "matches regex </a{3}/>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1464,41 +1397,6 @@ mod tests {
|
|||
assert!(!res.type_mismatch);
|
||||
assert_eq!(res.actual, "2020-03-09T22:18:26.625Z");
|
||||
assert_eq!(res.expected, "string with format YYYY-MM-DDTHH:mm:ss.sssZ");
|
||||
|
||||
// Some values from <https://datatracker.ietf.org/doc/html/rfc3339>
|
||||
let value = Value::String("1985-04-12T23:20:50.52Z".to_string());
|
||||
let res = eval_is_iso_date(&value).unwrap();
|
||||
assert!(res.success);
|
||||
|
||||
let value = Value::String("1996-12-19T16:39:57-08:00".to_string());
|
||||
let res = eval_is_iso_date(&value).unwrap();
|
||||
assert!(res.success);
|
||||
|
||||
let value = Value::String("1990-12-31T23:59:60Z".to_string());
|
||||
let res = eval_is_iso_date(&value).unwrap();
|
||||
assert!(res.success);
|
||||
|
||||
let value = Value::String("1990-12-31T15:59:60-08:00".to_string());
|
||||
let res = eval_is_iso_date(&value).unwrap();
|
||||
assert!(res.success);
|
||||
|
||||
let value = Value::String("1937-01-01T12:00:27.87+00:20".to_string());
|
||||
let res = eval_is_iso_date(&value).unwrap();
|
||||
assert!(res.success);
|
||||
|
||||
let value = Value::String("1978-01-15".to_string());
|
||||
let res = eval_is_iso_date(&value).unwrap();
|
||||
assert!(!res.success);
|
||||
assert!(!res.type_mismatch);
|
||||
assert_eq!(res.actual, "1978-01-15");
|
||||
assert_eq!(res.expected, "string with format YYYY-MM-DDTHH:mm:ss.sssZ");
|
||||
|
||||
let value = Value::Bool(true);
|
||||
let res = eval_is_iso_date(&value).unwrap();
|
||||
assert!(!res.success);
|
||||
assert!(res.type_mismatch);
|
||||
assert_eq!(res.actual, "boolean <true>");
|
||||
assert_eq!(res.expected, "string");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -65,6 +65,12 @@ pub enum ValueKind {
|
|||
Unit,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EvalError {
|
||||
Type,
|
||||
InvalidRegex,
|
||||
}
|
||||
|
||||
/// Equality of values
|
||||
/// as used in the predicate ==
|
||||
///
|
||||
|
|
|
|||
|
|
@ -17,22 +17,192 @@
|
|||
*/
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::Value;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TypeError;
|
||||
use super::{value::ValueKind, EvalError, Value};
|
||||
|
||||
impl Value {
|
||||
/// Compare with another value
|
||||
/// Compare with another value.
|
||||
///
|
||||
/// Returns TypeError if the values are not comparable
|
||||
pub fn compare(&self, other: &Value) -> Result<Ordering, TypeError> {
|
||||
/// Returns a [`EvalError::Type`] if the given value types are not supported.
|
||||
pub fn compare(&self, other: &Value) -> Result<Ordering, EvalError> {
|
||||
match (self, other) {
|
||||
(Value::String(s1), Value::String(s2)) => Ok(s1.cmp(s2)),
|
||||
(Value::Number(n1), Value::Number(n2)) => Ok(n1.cmp_value(n2)),
|
||||
_ => Err(TypeError),
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the value starts with the given prefix.
|
||||
///
|
||||
/// Returns `false` if it does not.
|
||||
///
|
||||
/// Returns a [`EvalError::Type`] if the given value types are not supported.
|
||||
pub fn starts_with(&self, other: &Value) -> Result<bool, EvalError> {
|
||||
match (self, other) {
|
||||
(Value::String(value), Value::String(prefix)) => Ok(value.starts_with(prefix)),
|
||||
(Value::Bytes(value), Value::Bytes(prefix)) => Ok(value.starts_with(prefix)),
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the value ends with the given suffix.
|
||||
///
|
||||
/// Returns `false` if it does not.
|
||||
///
|
||||
/// Returns a [`EvalError::Type`] if the given value types are not supported.
|
||||
pub fn ends_with(&self, other: &Value) -> Result<bool, EvalError> {
|
||||
match (self, other) {
|
||||
(Value::String(value), Value::String(suffix)) => Ok(value.ends_with(suffix)),
|
||||
(Value::Bytes(value), Value::Bytes(suffix)) => Ok(value.ends_with(suffix)),
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the value contains another value.
|
||||
///
|
||||
/// Returns `false` if it does not.
|
||||
///
|
||||
/// Returns a [`EvalError::Type`] if the given value types are not supported.
|
||||
pub fn contains(&self, other: &Value) -> Result<bool, EvalError> {
|
||||
match (self, other) {
|
||||
(Value::String(s), Value::String(substr)) => Ok(s.as_str().contains(substr.as_str())),
|
||||
|
||||
(Value::Bytes(s), Value::Bytes(substr)) => {
|
||||
Ok(contains(s.as_slice(), substr.as_slice()))
|
||||
}
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the list value includes another value.
|
||||
///
|
||||
/// Returns `false` if it does not.
|
||||
///
|
||||
/// Returns a [`EvalError::Type`] if the given value types are not supported.
|
||||
///
|
||||
/// TODO: deprecate method in favor of contains.
|
||||
pub fn includes(&self, other: &Value) -> Result<bool, EvalError> {
|
||||
match self {
|
||||
Value::List(values) => {
|
||||
let mut included = false;
|
||||
for v in values {
|
||||
if v == other {
|
||||
included = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(included)
|
||||
}
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` the value is a boolean.
|
||||
///
|
||||
/// Returns `false` if it is not.
|
||||
pub fn is_boolean(&self) -> bool {
|
||||
self.kind() == ValueKind::Bool
|
||||
}
|
||||
|
||||
/// Returns `true` the value is a collection.
|
||||
///
|
||||
/// Returns `false` if it is not.
|
||||
pub fn is_collection(&self) -> bool {
|
||||
self.kind() == ValueKind::Bytes
|
||||
|| self.kind() == ValueKind::List
|
||||
|| self.kind() == ValueKind::Nodeset
|
||||
|| self.kind() == ValueKind::Object
|
||||
}
|
||||
|
||||
/// Returns `true` the value is a date.
|
||||
///
|
||||
/// Returns `false` if it is not.
|
||||
pub fn is_date(&self) -> bool {
|
||||
self.kind() == ValueKind::Date
|
||||
}
|
||||
|
||||
/// Returns `true` the value is a float.
|
||||
///
|
||||
/// Returns `false` if it is not.
|
||||
pub fn is_float(&self) -> bool {
|
||||
self.kind() == ValueKind::Float
|
||||
}
|
||||
|
||||
/// Returns `true` the value is an integer.
|
||||
///
|
||||
/// Returns `false` if it is not.
|
||||
pub fn is_integer(&self) -> bool {
|
||||
self.kind() == ValueKind::Integer
|
||||
}
|
||||
|
||||
/// Returns `true` the value is a number.
|
||||
///
|
||||
/// Returns `false` if it is not.
|
||||
pub fn is_number(&self) -> bool {
|
||||
self.kind() == ValueKind::Integer || self.kind() == ValueKind::Float
|
||||
}
|
||||
|
||||
/// Returns `true` the value is a String.
|
||||
///
|
||||
/// Returns `false` if it is not.
|
||||
pub fn is_string(&self) -> bool {
|
||||
self.kind() == ValueKind::String || self.kind() == ValueKind::Secret
|
||||
}
|
||||
|
||||
/// Returns `true` the string value represents a RFC339 date (format YYYY-MM-DDTHH:mm:ss.sssZ).
|
||||
///
|
||||
/// Returns `false` if it does not.
|
||||
///
|
||||
/// Returns a [`EvalError::Type`] if the given value is not a String.
|
||||
pub fn is_iso_date(&self) -> Result<bool, EvalError> {
|
||||
match self {
|
||||
Value::String(value) => Ok(chrono::DateTime::parse_from_rfc3339(value).is_ok()),
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns count of the value.
|
||||
///
|
||||
/// Returns a [`EvalError::Type`] if the type of the value is not supported.
|
||||
pub fn count(&self) -> Result<usize, EvalError> {
|
||||
match self {
|
||||
Value::List(values) => Ok(values.len()),
|
||||
Value::String(data) => Ok(data.len()),
|
||||
Value::Nodeset(count) => Ok(*count),
|
||||
Value::Object(props) => Ok(props.len()),
|
||||
Value::Bytes(data) => Ok(data.len()),
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if and only if there is a match for the regex anywhere in the value.
|
||||
///
|
||||
/// Returns `false` otherwise.
|
||||
///
|
||||
/// Returns a [`EvalError::Type`] if the type of the value is not supported.
|
||||
///
|
||||
/// Returns an [`EvalError::InvalidRegex`] if the String is not a valid Regex.
|
||||
pub fn is_match(&self, other: &Value) -> Result<bool, EvalError> {
|
||||
let regex = match other {
|
||||
Value::String(s) => match regex::Regex::new(s.as_str()) {
|
||||
Ok(re) => re,
|
||||
Err(_) => return Err(EvalError::InvalidRegex),
|
||||
},
|
||||
Value::Regex(re) => re.clone(),
|
||||
_ => {
|
||||
return Err(EvalError::Type);
|
||||
}
|
||||
};
|
||||
match self {
|
||||
Value::String(value) => Ok(regex.is_match(value.as_str())),
|
||||
_ => Err(EvalError::Type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
|
||||
haystack
|
||||
.windows(needle.len())
|
||||
.any(|window| window == needle)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -61,15 +231,117 @@ mod tests {
|
|||
.unwrap(),
|
||||
Ordering::Greater
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_error() {
|
||||
assert_eq!(
|
||||
Value::Number(Number::Integer(1))
|
||||
.compare(&Value::Bool(true))
|
||||
.unwrap_err(),
|
||||
TypeError
|
||||
EvalError::Type
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_starts_with() {
|
||||
assert!(Value::String("Hello".to_string())
|
||||
.starts_with(&Value::String("H".to_string()))
|
||||
.unwrap());
|
||||
assert!(!Value::Bytes(vec![0, 1, 2])
|
||||
.starts_with(&Value::Bytes(vec![0, 2]))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ends_with() {
|
||||
assert!(Value::String("Hello".to_string())
|
||||
.ends_with(&Value::String("o".to_string()))
|
||||
.unwrap());
|
||||
assert!(!Value::Bytes(vec![0, 1, 2])
|
||||
.ends_with(&Value::Bytes(vec![0, 2]))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains() {
|
||||
let haystack = [1, 2, 3];
|
||||
assert!(contains(&haystack, &[1]));
|
||||
assert!(contains(&haystack, &[1, 2]));
|
||||
assert!(!contains(&haystack, &[1, 3]));
|
||||
assert!(Value::String("abc".to_string())
|
||||
.contains(&Value::String("ab".to_string()))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include() {
|
||||
let values = Value::List(vec![
|
||||
Value::Number(Number::Integer(0)),
|
||||
Value::Number(Number::Integer(2)),
|
||||
Value::Number(Number::Integer(3)),
|
||||
]);
|
||||
assert!(values.includes(&Value::Number(Number::Integer(0))).unwrap());
|
||||
assert!(!values.includes(&Value::Number(Number::Integer(4))).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type() {
|
||||
let value1 = Value::Bool(true);
|
||||
assert!(value1.is_boolean());
|
||||
assert!(!value1.is_collection());
|
||||
|
||||
let value2 = Value::Number(Number::Integer(1));
|
||||
assert!(!value2.is_boolean());
|
||||
assert!(!value2.is_collection());
|
||||
assert!(value2.is_number());
|
||||
assert!(value2.is_integer());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_date() {
|
||||
// Some values from <https://datatracker.ietf.org/doc/html/rfc3339>
|
||||
assert!(Value::String("1985-04-12T23:20:50.52Z".to_string())
|
||||
.is_iso_date()
|
||||
.unwrap());
|
||||
assert!(Value::String("1996-12-19T16:39:57-08:00".to_string())
|
||||
.is_iso_date()
|
||||
.unwrap());
|
||||
assert!(Value::String("1990-12-31T23:59:60Z".to_string())
|
||||
.is_iso_date()
|
||||
.unwrap());
|
||||
assert!(Value::String("1990-12-31T15:59:60-08:00".to_string())
|
||||
.is_iso_date()
|
||||
.unwrap());
|
||||
assert!(Value::String("1937-01-01T12:00:27.87+00:20".to_string())
|
||||
.is_iso_date()
|
||||
.unwrap());
|
||||
assert!(!Value::String("1978-01-15".to_string())
|
||||
.is_iso_date()
|
||||
.unwrap());
|
||||
assert_eq!(
|
||||
Value::Bool(true).is_iso_date().unwrap_err(),
|
||||
EvalError::Type
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_match() {
|
||||
let value = Value::String("hello".to_string());
|
||||
|
||||
let regex1 = Value::String("he.*".to_string());
|
||||
assert!(value.is_match(®ex1).unwrap());
|
||||
|
||||
let regex2 = Value::Regex(regex::Regex::new("he.*").unwrap());
|
||||
assert!(value.is_match(®ex2).unwrap());
|
||||
|
||||
let regex3 = Value::String("HE.*".to_string());
|
||||
assert!(!value.is_match(®ex3).unwrap());
|
||||
|
||||
let regex4 = Value::String("?HE.*".to_string());
|
||||
assert_eq!(
|
||||
value.is_match(®ex4).unwrap_err(),
|
||||
EvalError::InvalidRegex
|
||||
);
|
||||
|
||||
let regex5 = Value::Bool(true);
|
||||
assert_eq!(value.is_match(®ex5).unwrap_err(), EvalError::Type);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue