tauri-plugin-deeprespond-im 1.0.0-alpha

深应智能 IM 的 Tauri 插件:本地存储、安全存储、窗口间的锁与消息、系统事件、通知、角标、文件
//! 对象仓库的键(《1.本地存储》第 3 节):数字、字符串,或由它们组成的数组,比较规则与 IndexedDB 相同:
//! 数字 < 字符串 < 数组,数组逐项比较。键编码为可以按字节比较的 BLOB(memcmp 的顺序即键的顺序),
//! 与 Flutter SDK 的 keys.dart 逐字节相同,两边共用测试数据 `sdk/conformance/storage-keys.json`:
//! - 数字:0x10 + 8 字节(IEEE 754 双精度,正数翻转符号位,负数按位取反);
//! - 字符串:0x20 + UTF-8(0x00 写作 0x01 0x01,0x01 写作 0x01 0x02)+ 0x00;
//! - 数组:0x40 + 各项 + 0x00。

use serde::Deserialize;
use serde_json::Value;

use crate::error::{Error, Result};

const NUMBER: u8 = 0x10;
const STRING: u8 = 0x20;
const ARRAY: u8 = 0x40;

/// 编码一个键;不是合法的键(null、布尔、对象,或含有它们的数组)时返回 None。
pub fn encode_key(key: &Value) -> Option<Vec<u8>> {
    let mut out = Vec::with_capacity(16);
    encode_into(key, &mut out).then_some(out)
}

/// 同 [`encode_key`],不合法时以 local_validation(invalid_key)失败。
pub fn require_key(key: &Value) -> Result<Vec<u8>> {
    encode_key(key).ok_or_else(|| Error::validation("invalid_key", format!("不是合法的键:{}", short(key))))
}

fn encode_into(key: &Value, out: &mut Vec<u8>) -> bool {
    match key {
        Value::Number(n) => match n.as_f64() {
            // JSON 中没有 NaN;保险起见仍按不合法处理(与 isValidKey 相同)
            Some(f) if !f.is_nan() => {
                encode_number(f, out);
                true
            }
            _ => false,
        },
        Value::String(s) => {
            encode_string(s, out);
            true
        }
        Value::Array(items) => {
            out.push(ARRAY);
            for item in items {
                if !encode_into(item, out) {
                    return false;
                }
            }
            out.push(0x00);
            true
        }
        _ => false,
    }
}

fn encode_number(f: f64, out: &mut Vec<u8>) {
    let mut bytes = f.to_be_bytes();
    if bytes[0] & 0x80 == 0 {
        bytes[0] ^= 0x80;
    } else {
        for b in bytes.iter_mut() {
            *b = !*b;
        }
    }
    out.push(NUMBER);
    out.extend_from_slice(&bytes);
}

fn encode_string(s: &str, out: &mut Vec<u8>) {
    out.push(STRING);
    for &b in s.as_bytes() {
        match b {
            0x00 => out.extend_from_slice(&[0x01, 0x01]),
            0x01 => out.extend_from_slice(&[0x01, 0x02]),
            _ => out.push(b),
        }
    }
    out.push(0x00);
}

/// 一个编码后的键在 bytes 开头占的长度(游标中索引键与主键连在一起,据此拆开);格式不对时返回 None。
pub fn encoded_len(bytes: &[u8]) -> Option<usize> {
    match *bytes.first()? {
        NUMBER => (bytes.len() >= 9).then_some(9),
        // 转义后字符串中不会出现 0x00,第一个 0x00 就是结尾
        STRING => bytes.iter().position(|&b| b == 0x00).map(|end| end + 1),
        ARRAY => {
            let mut pos = 1;
            loop {
                match bytes.get(pos)? {
                    0x00 => return Some(pos + 1),
                    _ => pos += encoded_len(&bytes[pos..])?,
                }
            }
        }
        _ => None,
    }
}

/// 主键或索引的 keyPath:字符串(. 分隔层级)或字符串数组(复合键)。
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum KeyPath {
    One(String),
    Many(Vec<String>),
}

impl KeyPath {
    pub fn paths(&self) -> Vec<&str> {
        match self {
            KeyPath::One(p) => vec![p.as_str()],
            KeyPath::Many(ps) => ps.iter().map(String::as_str).collect(),
        }
    }
}

/// 按 keyPath 取出键并编码;取不到合法的键时返回 None(主键:记录不能写入;索引:这条记录不进入索引)。
/// 与 Flutter 的 extractKey 相同:只在对象中逐层取值,数组不按下标取。
pub fn extract_key(value: &Value, key_path: &KeyPath) -> Option<Vec<u8>> {
    let mut out = Vec::with_capacity(32);
    match key_path {
        KeyPath::One(path) => {
            if !encode_into(lookup(value, path)?, &mut out) {
                return None;
            }
        }
        KeyPath::Many(paths) => {
            out.push(ARRAY);
            for path in paths {
                if !encode_into(lookup(value, path)?, &mut out) {
                    return None;
                }
            }
            out.push(0x00);
        }
    }
    Some(out)
}

fn lookup<'a>(value: &'a Value, path: &str) -> Option<&'a Value> {
    let mut current = value;
    for segment in path.split('.') {
        current = current.as_object()?.get(segment)?;
    }
    Some(current)
}

/// 键的范围(闭区间,可以单边开)。JavaScript 交来的是 `{ lower?, upper?, lowerOpen?, upperOpen? }`。
#[derive(Clone, Debug, Default, Deserialize)]
pub struct KeyRangeArg {
    #[serde(default)]
    pub lower: Option<Value>,
    #[serde(default)]
    pub upper: Option<Value>,
    #[serde(default, rename = "lowerOpen", alias = "lower_open")]
    pub lower_open: bool,
    #[serde(default, rename = "upperOpen", alias = "upper_open")]
    pub upper_open: bool,
}

/// 编码后的范围:(键, 是否开区间)。
#[derive(Clone, Debug, Default)]
pub struct Range {
    pub lower: Option<(Vec<u8>, bool)>,
    pub upper: Option<(Vec<u8>, bool)>,
}

impl KeyRangeArg {
    pub fn encode(&self) -> Result<Range> {
        let bound = |v: &Option<Value>, open: bool| -> Result<Option<(Vec<u8>, bool)>> {
            match v {
                None | Some(Value::Null) => Ok(None),
                Some(key) => Ok(Some((require_key(key)?, open))),
            }
        };
        Ok(Range { lower: bound(&self.lower, self.lower_open)?, upper: bound(&self.upper, self.upper_open)? })
    }
}

/// 错误信息中只放键的前一小段,记录内容不进日志。
fn short(value: &Value) -> String {
    let text = value.to_string();
    match text.char_indices().nth(40) {
        Some((i, _)) => format!("{}…", &text[..i]),
        None => text,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    fn hex(v: Value) -> String {
        hex::encode(encode_key(&v).unwrap())
    }

    #[test]
    fn layout() {
        assert_eq!(hex(json!(0)), "108000000000000000");
        assert_eq!(hex(json!(1)), "10bff0000000000000");
        assert_eq!(hex(json!(-1)), "10400fffffffffffff");
        assert_eq!(hex(json!("")), "2000");
        assert_eq!(hex(json!("a\u{0}\u{1}")), "20610101010200");
        assert_eq!(hex(json!([])), "4000");
        assert_eq!(hex(json!([[]])), "40400000");
        assert!(encode_key(&json!(null)).is_none());
        assert!(encode_key(&json!(true)).is_none());
        assert!(encode_key(&json!({ "a": 1 })).is_none());
        assert!(encode_key(&json!([1, null])).is_none());
    }

    #[test]
    fn length_of_encoded_keys() {
        for key in [json!(5), json!("a\u{0}b"), json!([1, ["x", []], "y"]), json!([[]])] {
            let mut bytes = encode_key(&key).unwrap();
            let n = bytes.len();
            bytes.extend_from_slice(&encode_key(&json!("tail")).unwrap());
            assert_eq!(encoded_len(&bytes), Some(n), "{key}");
        }
        assert_eq!(encoded_len(&[0x10, 1, 2]), None);
        assert_eq!(encoded_len(&[0x40, 0x10]), None);
        assert_eq!(encoded_len(&[0x33]), None);
    }

    #[test]
    fn extract() {
        let v = json!({ "conversation_id": "c1", "seq": 5, "_local": { "created_ms": 7, "flag": true }, "list": [1] });
        let path = |s: &str| KeyPath::One(s.into());
        assert_eq!(extract_key(&v, &path("seq")), encode_key(&json!(5)));
        assert_eq!(extract_key(&v, &path("_local.created_ms")), encode_key(&json!(7)));
        assert_eq!(extract_key(&v, &KeyPath::Many(vec!["conversation_id".into(), "seq".into()])), encode_key(&json!(["c1", 5])));
        assert_eq!(extract_key(&v, &path("_local.flag")), None);
        assert_eq!(extract_key(&v, &path("missing")), None);
        assert_eq!(extract_key(&v, &path("list.0")), None);
        assert_eq!(extract_key(&v, &path("list")), encode_key(&json!([1])));
        assert_eq!(extract_key(&v, &KeyPath::Many(vec!["conversation_id".into(), "nope".into()])), None);
    }
}