error handling

This commit is contained in:
JMARyA 2024-02-09 10:46:36 +01:00
parent c9d1c372c9
commit efed4c0bea
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
2 changed files with 99 additions and 90 deletions

View file

@ -1,6 +1,5 @@
use serde_json::json;
mod test;
use serde_json::json;
fn less_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
if a.is_f64() {
@ -38,9 +37,49 @@ fn greater_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
}
}
pub fn matches(filter: &serde_json::Value, raw_obj: &serde_json::Value) -> bool {
#[derive(Debug)]
/// Represents errors that can occur while processing filters.
pub enum FilterError {
/// Indicates that the schema of the filter is invalid.
InvalidFilter,
/// Indicates that an unknown operator was encountered in the filter.
UnknownOperator,
/// Indicates that a key was not found in the object being filtered.
KeyNotFound,
}
/// 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
///
/// Returns `true` if the filter matches the object, otherwise `false`.
///
/// # Examples
///
/// ```
/// use serde_json::json;
/// use jsonfilter::matches;
///
/// let filter = json!({"name": "John", "age": 30});
/// let obj = json!({"name": "John", "age": 30, "city": "New York"});
///
/// assert!(matches(&filter, &obj));
/// ```
pub fn matches(filter: &serde_json::Value, obj: &serde_json::Value) -> bool {
try_matches(filter, obj).unwrap()
}
pub fn try_matches(
filter: &serde_json::Value,
obj: &serde_json::Value,
) -> Result<bool, FilterError> {
let filter = filter.as_object().unwrap();
let obj = raw_obj.as_object().unwrap();
let obj_map = obj.as_object().unwrap();
if filter.len() == 1 {
let filter_keys: Vec<_> = filter.keys().collect();
@ -51,27 +90,27 @@ pub fn matches(filter: &serde_json::Value, raw_obj: &serde_json::Value) -> bool
if let serde_json::Value::Array(and_list) = op_arg {
let and_list_bool: Vec<bool> = and_list
.iter()
.map(|sub_filter| matches(sub_filter, raw_obj))
.map(|sub_filter| matches(sub_filter, obj))
.collect();
return !and_list_bool.iter().any(|x| !x);
return Ok(!and_list_bool.iter().any(|x| !x));
} else {
return false;
return Err(FilterError::InvalidFilter);
}
}
"$or" => {
if let serde_json::Value::Array(or_list) = op_arg {
let or_list_bool: Vec<bool> = or_list
.iter()
.map(|sub_filter| matches(sub_filter, raw_obj))
.map(|sub_filter| matches(sub_filter, obj))
.collect();
return or_list_bool.iter().any(|x| *x);
return Ok(or_list_bool.iter().any(|x| *x));
} else {
return false;
return Err(FilterError::InvalidFilter);
}
}
_ => {
if op.starts_with("$") {
unimplemented!()
return Err(FilterError::UnknownOperator);
}
}
}
@ -81,33 +120,37 @@ pub fn matches(filter: &serde_json::Value, raw_obj: &serde_json::Value) -> bool
if val.is_object() {
let val_keys: Vec<_> = val.as_object().unwrap().keys().collect();
if val_keys.first().unwrap().starts_with("$") {
return match_operator(val, raw_obj, key.as_str());
return match_operator(val, obj, key.as_str());
} else {
// nested
for (_, _) in val.as_object().unwrap() {
let new_filter = filter.get(key).unwrap();
if let Some(val) = obj.get(key) {
return matches(new_filter, val);
if let Some(val) = obj_map.get(key) {
return try_matches(new_filter, val);
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
}
}
if let Some(valb) = obj.get(key) {
if let Some(valb) = obj_map.get(key) {
if val != valb {
return false;
return Ok(false);
}
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
true
Ok(true)
}
fn match_operator(val: &serde_json::Value, raw_obj: &serde_json::Value, key: &str) -> bool {
fn match_operator(
val: &serde_json::Value,
raw_obj: &serde_json::Value,
key: &str,
) -> Result<bool, FilterError> {
let obj = raw_obj.as_object().unwrap();
let val = val.as_object().unwrap();
if val.keys().len() == 1 {
@ -117,81 +160,77 @@ fn match_operator(val: &serde_json::Value, raw_obj: &serde_json::Value, key: &st
match op {
"$lt" => {
if let Some(a) = obj.get(key) {
return less_than_num(a, op_arg);
return Ok(less_than_num(a, op_arg));
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$lte" => {
if let Some(a) = obj.get(key) {
return less_than_num(a, op_arg) || a == op_arg;
return Ok(less_than_num(a, op_arg) || a == op_arg);
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$gt" => {
if let Some(valb) = obj.get(key) {
return greater_than_num(valb, op_arg);
return Ok(greater_than_num(valb, op_arg));
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$gte" => {
if let Some(a) = obj.get(key) {
return greater_than_num(a, op_arg) || a == op_arg;
return Ok(greater_than_num(a, op_arg) || a == op_arg);
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$not" => {
if let Some(inner) = val.get("$not") {
if let serde_json::Value::Object(inner) = inner {
let new_filter = json!({
key: inner
});
return !matches(&new_filter, raw_obj);
} else {
return false;
}
if let Some(serde_json::Value::Object(inner)) = val.get("$not") {
let new_filter = json!({
key: inner
});
return Ok(!try_matches(&new_filter, raw_obj)?);
} else {
return false;
return Err(FilterError::InvalidFilter);
}
}
"$ne" => {
if let Some(valb) = obj.get(key) {
return valb != op_arg;
return Ok(valb != op_arg);
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$in" => {
if let Some(valb) = obj.get(key) {
if let serde_json::Value::Array(list) = valb {
return list.iter().any(|x| x == op_arg);
return Ok(list.iter().any(|x| x == op_arg));
} else {
return false;
return Err(FilterError::InvalidFilter);
}
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$nin" => {
if let Some(valb) = obj.get(key) {
if let serde_json::Value::Array(list) = valb {
return !list.iter().any(|x| x == op_arg);
return Ok(!list.iter().any(|x| x == op_arg));
} else {
return false;
return Err(FilterError::InvalidFilter);
}
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$exists" => {
if let serde_json::Value::Bool(exists) = op_arg {
let valb = obj.get(key).is_some();
return *exists == valb;
return Ok(*exists == valb);
} else {
return false;
return Err(FilterError::InvalidFilter);
}
}
"$size" => {
@ -199,30 +238,30 @@ fn match_operator(val: &serde_json::Value, raw_obj: &serde_json::Value, key: &st
let val_size = list.len() as u64;
if let serde_json::Value::Number(pref_size) = op_arg {
let pref_size = pref_size.as_u64().unwrap();
return pref_size == val_size;
return Ok(pref_size == val_size);
} else {
return false;
return Err(FilterError::InvalidFilter);
}
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
"$regex" => {
if let serde_json::Value::String(regex_pattern) = op_arg {
if let Some(serde_json::Value::String(valb)) = obj.get(key) {
let pattern = regex::Regex::new(regex_pattern).unwrap();
return pattern.is_match(valb);
return Ok(pattern.is_match(valb));
} else {
return false;
return Err(FilterError::KeyNotFound);
}
} else {
return false;
return Err(FilterError::InvalidFilter);
}
}
"$type" => {
if let Some(valb) = obj.get(key) {
if let serde_json::Value::String(type_str) = op_arg {
return match type_str.to_lowercase().as_str() {
return Ok(match type_str.to_lowercase().as_str() {
"null" => valb.is_null(),
"string" => valb.is_string(),
"number" => valb.is_number(),
@ -230,21 +269,21 @@ fn match_operator(val: &serde_json::Value, raw_obj: &serde_json::Value, key: &st
"array" => valb.is_array(),
"boolean" => valb.is_boolean(),
_ => false,
};
});
} else {
return false;
return Err(FilterError::InvalidFilter);
}
} else {
return false;
return Err(FilterError::KeyNotFound);
}
}
_ => {
if op.starts_with("$") {
unimplemented!()
return Err(FilterError::UnknownOperator);
}
}
}
}
return false;
return Ok(false);
}

View file

@ -1,30 +0,0 @@
use jsonfilter::matches;
use serde_json::json;
fn main() {
assert!(matches(
&json!({
"$and": [
{ "key": "value" },
{ "num": 5 }
]
}),
&json!({
"key": "value",
"num": 5
})
));
assert!(!matches(
&json!({
"$and": [
{ "key": "value" },
{ "num": 5 }
]
}),
&json!({
"key": "value",
"num": 3
})
));
}