refactor + size_cmp

This commit is contained in:
JMARyA 2024-06-05 11:38:38 +02:00
parent a371b29b50
commit a023352b0a
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
4 changed files with 129 additions and 8 deletions

2
Cargo.lock generated
View file

@ -19,7 +19,7 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jsonfilter"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"regex",
"serde",

View file

@ -2,7 +2,7 @@
name = "jsonfilter"
description = "Filter and compare JSON objects"
authors = ["JMARyA <jmarya@hydrar.de>"]
version = "0.2.0"
version = "0.2.1"
edition = "2021"
keywords = ["json", "filter"]
license = "MIT"

View file

@ -68,7 +68,22 @@ pub fn order(a: &serde_json::Value, b: &serde_json::Value) -> std::cmp::Ordering
}
}
fn less_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
/// Compares two `serde_json::Value` objects and determines if the first value is less than the second value.
///
/// # Arguments
///
/// * `a` - A reference to the first `serde_json::Value` to be compared. This can be a floating-point number, integer, unsigned integer, or a string.
/// * `b` - A reference to the second `serde_json::Value` to be compared. This should be of the same type as `a`.
///
/// # Returns
///
/// * `true` if `a` is less than `b`.
/// * `false` otherwise.
///
/// # Panics
///
/// This function will panic if `a` and `b` are not of the same type or if they are of an unsupported type.
fn less_than_json(a: &serde_json::Value, b: &serde_json::Value) -> bool {
if a.is_f64() {
let a = a.as_f64().unwrap();
let b = b.as_f64().unwrap();
@ -90,7 +105,22 @@ fn less_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
}
}
fn greater_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
/// Compares two `serde_json::Value` objects and determines if the first value is greater than the second value.
///
/// # Arguments
///
/// * `a` - A reference to the first `serde_json::Value` to be compared. This can be a floating-point number, integer, unsigned integer, or a string.
/// * `b` - A reference to the second `serde_json::Value` to be compared. This should be of the same type as `a`.
///
/// # Returns
///
/// * `true` if `a` is greater than `b`.
/// * `false` otherwise.
///
/// # Panics
///
/// This function will panic if `a` and `b` are not of the same type or if they are of an unsupported type.
fn greater_than_json(a: &serde_json::Value, b: &serde_json::Value) -> bool {
if a.is_f64() {
let a = a.as_f64().unwrap();
let b = b.as_f64().unwrap();
@ -150,6 +180,18 @@ pub fn matches(filter: &serde_json::Value, obj: &serde_json::Value) -> bool {
try_matches(filter, obj).unwrap()
}
/// Matches a filter against a raw object and returns a boolean indicating if the filter matches the object.
///
/// # Arguments
///
/// * `filter` - A reference to a `serde_json::Value` representing the filter to apply. Must be an object.
/// * `obj` - A reference to a `serde_json::Value` representing the object to match against. Must be an object.
///
/// # Returns
///
/// * `Ok(true)` if the object matches the filter criteria.
/// * `Ok(false)` if the object does not match the filter criteria.
/// * `Err(FilterError)` if there is an error in the filtering process.
pub fn try_matches(
filter: &serde_json::Value,
obj: &serde_json::Value,
@ -157,6 +199,7 @@ pub fn try_matches(
let filter = filter.as_object().unwrap();
let obj_map = obj.as_object().unwrap();
// Handle the case where the filter has a single key, such as top level $and, $or, $not
if filter.len() == 1 {
let filter_keys: Vec<_> = filter.keys().collect();
let op = filter_keys.first().unwrap();
@ -209,6 +252,7 @@ pub fn try_matches(
if val.is_object() {
let val_keys: Vec<_> = val.as_object().unwrap().keys().collect();
if val_keys.first().unwrap().starts_with('$') {
// handle operators
conditions.push(match_operator(val, obj, key.as_str()));
} else {
// nested
@ -224,6 +268,7 @@ pub fn try_matches(
continue;
}
// Compare simple key-value pairs
if let Some(valb) = obj_map.get(key) {
if val != valb {
conditions.push(Ok(false));
@ -236,6 +281,21 @@ pub fn try_matches(
check(&conditions)
}
/// Checks if all conditions in the given list are met.
///
/// This function iterates through a list of `Result<bool, FilterError>` conditions, checking for errors first.
/// If any condition is an error, it returns that error. Otherwise, it returns `Ok(true)` if all conditions are `true`
/// and `Ok(false)` if any condition is `false`.
///
/// # Arguments
///
/// * `conditions` - A slice of `Result<bool, FilterError>` representing the conditions to be checked.
///
/// # Returns
///
/// * `Ok(true)` if all conditions are true.
/// * `Ok(false)` if any condition is false.
/// * `Err(FilterError)` if any condition is an error.
fn check(conditions: &[Result<bool, FilterError>]) -> Result<bool, FilterError> {
conditions.iter().find(|x| x.is_err()).map_or_else(
|| Ok(!conditions.iter().map(|x| x.unwrap()).any(|x| !x)),
@ -243,6 +303,27 @@ fn check(conditions: &[Result<bool, FilterError>]) -> Result<bool, FilterError>
)
}
/// Matches a filter operator against a key-value pair in the object and determines if the condition is met.
///
/// # Arguments
///
/// * `val` - The value associated with the operator in the filter. This should be a JSON object.
/// * `raw_obj` - The object to be filtered. This should be a JSON object.
/// * `key` - The key in the object that the filter operator applies to.
///
/// # Returns
///
/// * `Ok(true)` if the condition specified by the filter operator is met.
/// * `Ok(false)` if the condition specified by the filter operator is not met.
/// * `Err(FilterError)` if there is an error in the filtering process.
///
/// # Errors
///
/// This function will return an error if:
/// * The filter or object is not a valid JSON object.
/// * An unknown operator is used in the filter.
/// * A required key is not found in the object.
/// * The filter format is invalid.
fn match_operator(
val: &serde_json::Value,
raw_obj: &serde_json::Value,
@ -250,6 +331,7 @@ fn match_operator(
) -> Result<bool, FilterError> {
let obj = raw_obj.as_object().unwrap();
let val = val.as_object().unwrap();
if val.keys().len() == 1 {
let keys: Vec<_> = val.keys().collect();
let op = keys.first().unwrap().as_str();
@ -283,25 +365,25 @@ fn match_operator(
}
"$lt" => {
if let Some(a) = obj.get(key) {
return Ok(less_than_num(a, op_arg));
return Ok(less_than_json(a, op_arg));
}
return Err(FilterError::KeyNotFound);
}
"$lte" => {
if let Some(a) = obj.get(key) {
return Ok(less_than_num(a, op_arg) || a == op_arg);
return Ok(less_than_json(a, op_arg) || a == op_arg);
}
return Err(FilterError::KeyNotFound);
}
"$gt" => {
if let Some(valb) = obj.get(key) {
return Ok(greater_than_num(valb, op_arg));
return Ok(greater_than_json(valb, op_arg));
}
return Err(FilterError::KeyNotFound);
}
"$gte" => {
if let Some(a) = obj.get(key) {
return Ok(greater_than_num(a, op_arg) || a == op_arg);
return Ok(greater_than_json(a, op_arg) || a == op_arg);
}
return Err(FilterError::KeyNotFound);
}
@ -352,6 +434,28 @@ fn match_operator(
let pref_size = pref_size.as_u64().unwrap();
return Ok(pref_size == val_size);
}
if let serde_json::Value::Object(s_op_obj) = op_arg {
if s_op_obj.len() == 1 {
let keys: Vec<_> = s_op_obj.keys().collect();
let key = keys.first().unwrap();
let val = s_op_obj.get(*key).unwrap().as_u64().unwrap();
match key.as_str() {
"$gt" => {
return Ok(val_size > val);
}
"$gte" => {
return Ok(val_size >= val);
}
"$lt" => {
return Ok(val_size < val);
}
"$lte" => {
return Ok(val_size <= val);
}
_ => {}
}
}
}
return Err(FilterError::InvalidFilter);
}
return Err(FilterError::KeyNotFound);

View file

@ -359,6 +359,23 @@ mod tests {
));
}
#[test]
fn array_size_cmp() {
let empty: Vec<i32> = vec![];
let one = vec![1];
let two = vec![1, 2];
let many = vec![1, 2, 3, 4, 5, 6];
let filter = json!({
"list": {"$size": {"$gt": 0}}
});
assert!(!matches(&filter, &json!({"list": empty})));
assert!(matches(&filter, &json!({"list": one})));
assert!(matches(&filter, &json!({"list": two})));
assert!(matches(&filter, &json!({"list": many})));
}
#[test]
fn nested_modifier() {
assert!(matches(