use serde_json::{json, Map, Value};
pub const KEY_OID: &str = "$oid";
pub const KEY_DATE: &str = "$date";
pub const KEY_DECIMAL: &str = "$numberDecimal";
pub const KEY_LONG: &str = "$numberLong";
pub const KEY_BINARY: &str = "$binary";
pub fn object_id(hex: &str) -> Value {
json!({ KEY_OID: hex.to_ascii_lowercase() })
}
pub fn date(ms: i64) -> Value {
json!({ KEY_DATE: ms })
}
pub fn decimal128(s: &str) -> Value {
json!({ KEY_DECIMAL: s })
}
pub fn number_long(v: i64) -> Value {
json!({ KEY_LONG: v.to_string() })
}
pub fn as_object_id(v: &Value) -> Option<&str> {
v.as_object()?.get(KEY_OID)?.as_str()
}
pub fn as_date_ms(v: &Value) -> Option<i64> {
v.as_object()?.get(KEY_DATE)?.as_i64()
}
pub fn is_extended(v: &Value) -> bool {
match v.as_object() {
None => false,
Some(o) if o.len() != 1 => false,
Some(o) => {
let k = o.keys().next().map(String::as_str).unwrap_or("");
matches!(k, KEY_OID | KEY_DATE | KEY_DECIMAL | KEY_LONG | KEY_BINARY)
}
}
}
pub fn canonicalize(v: &Value) -> Value {
match v {
Value::Array(a) => Value::Array(a.iter().map(canonicalize).collect()),
Value::Object(o) => {
if is_extended(v) {
if let Some((k, inner)) = o.iter().next() {
return match (k.as_str(), inner) {
(KEY_OID, Value::String(s)) => object_id(s),
_ => {
let mut m = Map::new();
m.insert(k.clone(), inner.clone());
Value::Object(m)
}
};
}
}
Value::Object(
o.iter()
.map(|(k, x)| (k.clone(), canonicalize(x)))
.collect(),
)
}
other => other.clone(),
}
}
pub fn id_key(v: &Value) -> String {
match v {
Value::String(s) => s.clone(),
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::Object(_) => as_object_id(v).map(String::from).unwrap_or_else(|| dump(v)),
Value::Array(_) => dump(v),
}
}
fn dump(v: &Value) -> String {
serde_json::to_string(v).unwrap_or_default()
}