use serde_json::Value as Json;
use crate::collection::Update;
use crate::document::{Document, Value};
use crate::error::TalaDbError;
use crate::json_depth::check_json_depth;
use crate::query::Filter;
pub fn to_value(j: &Json) -> Value {
match j {
Json::Null => Value::Null,
Json::Bool(b) => Value::Bool(*b),
Json::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Int(i)
} else {
Value::Float(n.as_f64().unwrap_or(0.0))
}
}
Json::String(s) => Value::Str(s.clone()),
Json::Array(arr) => Value::Array(arr.iter().map(to_value).collect()),
Json::Object(map) => {
Value::Object(map.iter().map(|(k, v)| (k.clone(), to_value(v))).collect())
}
}
}
pub fn from_value(v: &Value) -> Json {
match v {
Value::Null => Json::Null,
Value::Bool(b) => Json::Bool(*b),
Value::Int(n) => Json::Number((*n).into()),
Value::Float(f) => serde_json::Number::from_f64(*f)
.map(Json::Number)
.unwrap_or(Json::Null),
Value::Str(s) => Json::String(s.clone()),
Value::Bytes(b) => Json::String(format!("<bytes:{}>", b.len())),
Value::Array(arr) => Json::Array(arr.iter().map(from_value).collect()),
Value::Object(obj) => Json::Object(
obj.iter()
.map(|(k, v)| (k.clone(), from_value(v)))
.collect(),
),
}
}
pub fn document_to_json(doc: &Document) -> Json {
let mut map = serde_json::Map::new();
map.insert("_id".to_string(), Json::String(doc.id.to_string()));
for (k, v) in &doc.fields {
map.insert(k.clone(), from_value(v));
}
Json::Object(map)
}
pub fn fields_from_json(v: &Json) -> Result<Vec<(String, Value)>, TalaDbError> {
check_json_depth(v)?;
let obj = v
.as_object()
.ok_or_else(|| TalaDbError::Serialization("a document must be a JSON object".into()))?;
Ok(obj.iter().map(|(k, v)| (k.clone(), to_value(v))).collect())
}
pub fn filter_from_json(v: &Json) -> Result<Filter, TalaDbError> {
check_json_depth(v)?;
if v.is_null() || v.as_object().is_some_and(serde_json::Map::is_empty) {
return Ok(Filter::All);
}
filter_from_json_object(v).ok_or_else(|| TalaDbError::InvalidFilter(v.to_string()))
}
#[doc(hidden)]
pub fn filter_from_json_object(v: &Json) -> Option<Filter> {
let obj = v.as_object()?;
let mut filters: Vec<Filter> = Vec::new();
for (field, expr) in obj {
if field.starts_with('$') {
let logical = match field.as_str() {
"$and" => Filter::And(
expr.as_array()?
.iter()
.map(filter_from_json_object)
.collect::<Option<_>>()?,
),
"$or" => Filter::Or(
expr.as_array()?
.iter()
.map(filter_from_json_object)
.collect::<Option<_>>()?,
),
"$not" => Filter::Not(Box::new(filter_from_json_object(expr)?)),
_ => return None,
};
filters.push(logical);
continue;
}
if !expr.is_object() {
filters.push(Filter::Eq(field.clone(), to_value(expr)));
continue;
}
let ops = expr.as_object()?;
if ops.is_empty() {
return None;
}
for (op, val) in ops {
let v = to_value(val);
let f = match op.as_str() {
"$eq" => Filter::Eq(field.clone(), v),
"$ne" => Filter::Ne(field.clone(), v),
"$gt" => Filter::Gt(field.clone(), v),
"$gte" => Filter::Gte(field.clone(), v),
"$lt" => Filter::Lt(field.clone(), v),
"$lte" => Filter::Lte(field.clone(), v),
"$exists" => Filter::Exists(field.clone(), val.as_bool()?),
"$in" => Filter::In(
field.clone(),
val.as_array()?.iter().map(to_value).collect(),
),
"$nin" => Filter::Nin(
field.clone(),
val.as_array()?.iter().map(to_value).collect(),
),
"$contains" => Filter::Contains(field.clone(), val.as_str()?.to_string()),
"$regex" => Filter::Regex(field.clone(), val.as_str()?.to_string()),
_ => return None,
};
filters.push(f);
}
}
match filters.len() {
0 => Some(Filter::All),
1 => Some(filters.remove(0)),
_ => Some(Filter::And(filters)),
}
}
pub fn update_from_json(v: &Json) -> Result<Update, TalaDbError> {
update_from_json_object(v)
.ok_or_else(|| TalaDbError::InvalidOperation(format!("invalid update: {v}")))
}
fn update_from_json_object(v: &Json) -> Option<Update> {
let obj = v.as_object()?;
let mut updates = Vec::new();
if let Some(set) = obj.get("$set") {
let pairs = set
.as_object()?
.iter()
.map(|(k, v)| (k.clone(), to_value(v)))
.collect();
updates.push(Update::Set(pairs));
}
if let Some(unset) = obj.get("$unset") {
let keys = unset.as_object()?.keys().cloned().collect();
updates.push(Update::Unset(keys));
}
if let Some(inc) = obj.get("$inc") {
let pairs = inc
.as_object()?
.iter()
.map(|(k, v)| (k.clone(), to_value(v)))
.collect();
updates.push(Update::Inc(pairs));
}
if let Some(push) = obj.get("$push") {
updates.extend(
push.as_object()?
.iter()
.map(|(k, v)| Update::Push(k.clone(), to_value(v))),
);
}
if let Some(pull) = obj.get("$pull") {
updates.extend(
pull.as_object()?
.iter()
.map(|(k, v)| Update::Pull(k.clone(), to_value(v))),
);
}
if obj
.keys()
.any(|k| !matches!(k.as_str(), "$set" | "$unset" | "$inc" | "$push" | "$pull"))
{
return None;
}
match updates.len() {
0 => None,
1 => Some(updates.remove(0)),
_ => Some(Update::Many(updates)),
}
}