Skip to main content

rust_store_core/
datasource.rs

1//! 数据源(DataSource)—— 多后端路由的纯逻辑
2//!
3//! 一个 schema 绑定到一个「数据源」(source 名);数据源配置把 source 名解析为
4//! [`DataSource`](Mongo 原生 / 某个 SQL 后端)。本模块**只做解析**:不持有连接、
5//! 不做任何 IO(连接句柄留在 Host,参见铁律 1)。
6//!
7//! 配置 JSON 形状(Host 只把 kind 传进来,连接句柄不出 Host):
8//!
9//! ```json
10//! { "sources": { "default": "mongo", "mysql1": "mysql", "pg1": "postgres" } }
11//! ```
12//!
13//! schema 定义里的 `datasource` 字段是 **source 名**(可选):
14//! - 缺省 → 名为 [`DEFAULT_SOURCE`] 的数据源;
15//! - 若配置未声明 `default` → 回落到单源 Mongo(保证既有调用方零变更)。
16
17use serde_json::Value;
18
19use crate::dialect::Backend;
20
21/// schema 未显式声明 `datasource` 时使用的 source 名
22pub const DEFAULT_SOURCE: &str = "default";
23
24/// 一个 schema 的底层数据源
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum DataSource {
27    /// MongoDB —— 走 Mongo 原生驱动路径
28    Mongo,
29    /// 关系型 SQL 后端 —— 命令经 dialect 翻译后执行
30    Sql(Backend),
31    /// 本地磁盘文档存储 —— 集合以 JSON 文件落盘,命令经 core 本地求值器执行
32    Local,
33}
34
35impl DataSource {
36    /// 从数据源 kind 字符串解析(未知 → Err)
37    pub fn from_kind(kind: &str) -> Result<DataSource, String> {
38        match kind.to_lowercase().as_str() {
39            "mongo" | "mongodb" => Ok(DataSource::Mongo),
40            "local" => Ok(DataSource::Local),
41            // memory 与 local 同族:core 只关心「求值路径相同」(均走 core 本地求值器),
42            // 介质差异由宿主按描述符 kind 判别;故 as_str() 对两者都返回 "local"。
43            "memory" => Ok(DataSource::Local),
44            other => Backend::parse(other).map(DataSource::Sql),
45        }
46    }
47
48    /// 数据源 kind(与配置里的字符串一致,供绑定层序列化)
49    pub fn as_str(&self) -> &'static str {
50        match self {
51            DataSource::Mongo => "mongo",
52            DataSource::Sql(b) => b.as_str(),
53            DataSource::Local => "local",
54        }
55    }
56
57    pub fn is_mongo(&self) -> bool {
58        matches!(self, DataSource::Mongo)
59    }
60
61    pub fn is_local(&self) -> bool {
62        matches!(self, DataSource::Local)
63    }
64
65    /// SQL 后端(Mongo / Local 时为 `None`)
66    pub fn backend(&self) -> Option<Backend> {
67        match self {
68            DataSource::Sql(b) => Some(*b),
69            DataSource::Mongo | DataSource::Local => None,
70        }
71    }
72}
73
74/// 数据源配置:source 名 → [`DataSource`]
75#[derive(Debug, Clone, Default)]
76pub struct DataSourceConfig {
77    /// 保持声明顺序(便于确定性输出与对拍)
78    sources: Vec<(String, DataSource)>,
79}
80
81impl DataSourceConfig {
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    /// 从 JSON 解析:`{ "sources": { "<name>": "<kind>" } }`
87    ///
88    /// `null` / 无 `sources` → 空配置(等价单源 Mongo)。
89    pub fn from_json(v: &Value) -> Result<Self, String> {
90        let mut cfg = Self::new();
91        let Some(obj) = v.as_object() else {
92            return Ok(cfg);
93        };
94        if let Some(Value::Object(sources)) = obj.get("sources") {
95            for (name, kind) in sources {
96                let kind = kind
97                    .as_str()
98                    .ok_or_else(|| format!("数据源 {} 的 kind 必须是字符串", name))?;
99                cfg.register(name, DataSource::from_kind(kind)?);
100            }
101        }
102        Ok(cfg)
103    }
104
105    /// 注册 / 覆盖一个数据源
106    pub fn register(&mut self, name: &str, source: DataSource) {
107        if let Some(slot) = self.sources.iter_mut().find(|(n, _)| n == name) {
108            slot.1 = source;
109        } else {
110            self.sources.push((name.to_string(), source));
111        }
112    }
113
114    pub fn get(&self, name: &str) -> Option<DataSource> {
115        self.sources
116            .iter()
117            .find(|(n, _)| n == name)
118            .map(|(_, s)| *s)
119    }
120
121    /// 解析 schema 声明的 source 名 → [`DataSource`]
122    ///
123    /// - `None`(schema 未声明)→ 名为 [`DEFAULT_SOURCE`] 的数据源;配置未声明则回落 Mongo
124    /// - `Some(name)` → 必须已在配置中声明,否则 Err(避免静默走错库)
125    pub fn resolve(&self, source: Option<&str>) -> Result<DataSource, String> {
126        match source {
127            None => Ok(self.get(DEFAULT_SOURCE).unwrap_or(DataSource::Mongo)),
128            Some(name) => self
129                .get(name)
130                .ok_or_else(|| format!("schema 绑定的数据源未在配置中声明: {}", name)),
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use serde_json::json;
139
140    #[test]
141    fn parse_and_resolve() {
142        let cfg = DataSourceConfig::from_json(&json!({
143            "sources": { "default": "mongo", "mysql1": "mysql", "pg1": "postgres", "lite": "sqlite" }
144        }))
145        .unwrap();
146
147        assert_eq!(cfg.resolve(None).unwrap(), DataSource::Mongo);
148        assert_eq!(
149            cfg.resolve(Some("mysql1")).unwrap(),
150            DataSource::Sql(Backend::Mysql)
151        );
152        assert_eq!(
153            cfg.resolve(Some("pg1")).unwrap(),
154            DataSource::Sql(Backend::Postgres)
155        );
156        assert_eq!(
157            cfg.resolve(Some("lite")).unwrap(),
158            DataSource::Sql(Backend::Sqlite)
159        );
160    }
161
162    #[test]
163    fn empty_config_defaults_to_mongo() {
164        let cfg = DataSourceConfig::from_json(&Value::Null).unwrap();
165        assert_eq!(cfg.resolve(None).unwrap(), DataSource::Mongo);
166        // 未声明 default 时,显式 source 名仍报错
167        assert!(cfg.resolve(Some("missing")).is_err());
168    }
169
170    #[test]
171    fn unknown_kind_and_source_error() {
172        assert!(DataSourceConfig::from_json(&json!({ "sources": { "x": "oracle" } })).is_err());
173        let cfg =
174            DataSourceConfig::from_json(&json!({ "sources": { "default": "mongo" } })).unwrap();
175        assert!(cfg.resolve(Some("nope")).is_err());
176    }
177
178    #[test]
179    fn kind_roundtrip() {
180        assert_eq!(DataSource::from_kind("mongodb").unwrap().as_str(), "mongo");
181        assert_eq!(DataSource::from_kind("MySQL").unwrap().as_str(), "mysql");
182        assert_eq!(
183            DataSource::from_kind("pg").unwrap().backend(),
184            Some(Backend::Postgres)
185        );
186        assert!(DataSource::Mongo.is_mongo());
187    }
188
189    #[test]
190    fn local_kind_roundtrip() {
191        let ds = DataSource::from_kind("local").unwrap();
192        assert_eq!(ds, DataSource::Local);
193        assert_eq!(ds.as_str(), "local");
194        assert!(ds.backend().is_none());
195        assert!(ds.is_local());
196        assert!(!DataSource::Mongo.is_local());
197        // 未知 kind 仍报错
198        assert!(DataSource::from_kind("oracle").is_err());
199    }
200
201    #[test]
202    fn memory_kind_alias() {
203        let ds = DataSource::from_kind("memory").unwrap();
204        assert_eq!(ds, DataSource::Local);
205        assert!(ds.is_local());
206        // round-trip 不对称是刻意的:memory 在 core 侧与 local 同族,as_str() 归一为 "local"
207        assert_eq!(ds.as_str(), "local");
208        assert!(ds.backend().is_none());
209    }
210}