Skip to main content

rust_store_core/
bson.rs

1//! BSON 跨 FFI 线格式契约
2//!
3//! core 不持有驱动,Host 用原生驱动执行 Command 后把**原始文档**回喂 core 做后处理。
4//! 但 `serde_json::Value` 无法表达 `ObjectId` / `Date` / `Decimal128` / `Long`,
5//! 因此双方约定一套 **Extended JSON(canonical 子集)** 线格式:
6//!
7//! | BSON 类型   | 线格式                          |
8//! |-------------|---------------------------------|
9//! | ObjectId    | `{"$oid": "<24位小写hex>"}`     |
10//! | Date        | `{"$date": <毫秒整数>}`          |
11//! | Decimal128  | `{"$numberDecimal": "<字符串>"}` |
12//! | Long/BigInt | `{"$numberLong": "<字符串>"}`    |
13//! | Binary      | `{"$binary": {"base64": "..", "subType": ".."}}` |
14//!
15//! 约定:
16//!   1. Host → core 方向必须调 [`canonicalize`] 归一化(ObjectId 小写、`$date` 统一为毫秒数值);
17//!   2. core → Host 方向由本模块的构造函数产出,保证形状固定;
18//!   3. 其余 JSON 类型原样透传,键序不作为契约(serde_json 默认按字典序)。
19
20use serde_json::{json, Map, Value};
21
22pub const KEY_OID: &str = "$oid";
23pub const KEY_DATE: &str = "$date";
24pub const KEY_DECIMAL: &str = "$numberDecimal";
25pub const KEY_LONG: &str = "$numberLong";
26pub const KEY_BINARY: &str = "$binary";
27
28/// ObjectId:入参为 24 位 hex,统一转小写
29pub fn object_id(hex: &str) -> Value {
30    json!({ KEY_OID: hex.to_ascii_lowercase() })
31}
32
33/// 日期:毫秒时间戳(与 JS `Date.now()` 同单位)
34pub fn date(ms: i64) -> Value {
35    json!({ KEY_DATE: ms })
36}
37
38/// Decimal128:字符串形式保留精度
39pub fn decimal128(s: &str) -> Value {
40    json!({ KEY_DECIMAL: s })
41}
42
43/// 64 位整数:字符串形式避免 JSON number 精度丢失
44pub fn number_long(v: i64) -> Value {
45    json!({ KEY_LONG: v.to_string() })
46}
47
48/// 取 ObjectId 的 hex 字符串
49pub fn as_object_id(v: &Value) -> Option<&str> {
50    v.as_object()?.get(KEY_OID)?.as_str()
51}
52
53/// 取日期的毫秒值
54pub fn as_date_ms(v: &Value) -> Option<i64> {
55    v.as_object()?.get(KEY_DATE)?.as_i64()
56}
57
58/// 判断是否为扩展类型对象(`$oid` / `$date` / ... 之一)
59pub fn is_extended(v: &Value) -> bool {
60    match v.as_object() {
61        None => false,
62        Some(o) if o.len() != 1 => false,
63        Some(o) => {
64            let k = o.keys().next().map(String::as_str).unwrap_or("");
65            matches!(k, KEY_OID | KEY_DATE | KEY_DECIMAL | KEY_LONG | KEY_BINARY)
66        }
67    }
68}
69
70/// 递归归一化扩展类型(幂等),用于 Host 回喂文档前统一形状
71///
72/// 归一化规则:
73///   - `{"$oid": hex}` → hex 转小写;
74///   - `{"$date": n}` → n 为整数则保留;为字符串(ISO)时保留原样,由 Host 保证已是毫秒;
75///   - 其余扩展类型透传。
76pub fn canonicalize(v: &Value) -> Value {
77    match v {
78        Value::Array(a) => Value::Array(a.iter().map(canonicalize).collect()),
79        Value::Object(o) => {
80            if is_extended(v) {
81                // is_extended 保证恰好一个键;仍用 checked 取值,避免不变量漂移后 panic
82                if let Some((k, inner)) = o.iter().next() {
83                    return match (k.as_str(), inner) {
84                        (KEY_OID, Value::String(s)) => object_id(s),
85                        _ => {
86                            let mut m = Map::new();
87                            m.insert(k.clone(), inner.clone());
88                            Value::Object(m)
89                        }
90                    };
91                }
92            }
93            Value::Object(
94                o.iter()
95                    .map(|(k, x)| (k.clone(), canonicalize(x)))
96                    .collect(),
97            )
98        }
99        other => other.clone(),
100    }
101}
102
103/// 文档主键的可比较字符串形式,对应 JS `String(id)`
104///
105/// 两阶段查询需按阶段一返回的 `_id` 顺序还原排序,而 ObjectId 在 JS 里
106/// `String(id)` 即其 hex 值。
107pub fn id_key(v: &Value) -> String {
108    match v {
109        Value::String(s) => s.clone(),
110        Value::Null => "null".to_string(),
111        Value::Bool(b) => b.to_string(),
112        Value::Number(n) => n.to_string(),
113        Value::Object(_) => as_object_id(v).map(String::from).unwrap_or_else(|| dump(v)),
114        Value::Array(_) => dump(v),
115    }
116}
117
118fn dump(v: &Value) -> String {
119    serde_json::to_string(v).unwrap_or_default()
120}