diff --git a/Cargo.lock b/Cargo.lock index 21b3ce3..15e5b21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jsonfilter" -version = "0.2.0" +version = "0.2.1" dependencies = [ "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index ac7a308..bfc8477 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "jsonfilter" description = "Filter and compare JSON objects" authors = ["JMARyA "] -version = "0.2.0" +version = "0.2.1" edition = "2021" keywords = ["json", "filter"] license = "MIT" diff --git a/src/lib.rs b/src/lib.rs index a317432..4101ee5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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` 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` 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]) -> Result { 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]) -> Result ) } +/// 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 { 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); diff --git a/src/test.rs b/src/test.rs index 4a33182..7677fda 100644 --- a/src/test.rs +++ b/src/test.rs @@ -359,6 +359,23 @@ mod tests { )); } + #[test] + fn array_size_cmp() { + let empty: Vec = 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(