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            other => Backend::parse(other).map(DataSource::Sql),
42        }
43    }
44
45    /// 数据源 kind(与配置里的字符串一致,供绑定层序列化)
46    pub fn as_str(&self) -> &'static str {
47        match self {
48            DataSource::Mongo => "mongo",
49            DataSource::Sql(b) => b.as_str(),
50            DataSource::Local => "local",
51        }
52    }
53
54    pub fn is_mongo(&self) -> bool {
55        matches!(self, DataSource::Mongo)
56    }
57
58    pub fn is_local(&self) -> bool {
59        matches!(self, DataSource::Local)
60    }
61
62    /// SQL 后端(Mongo / Local 时为 `None`)
63    pub fn backend(&self) -> Option<Backend> {
64        match self {
65            DataSource::Sql(b) => Some(*b),
66            DataSource::Mongo | DataSource::Local => None,
67        }
68    }
69}
70
71/// 数据源配置:source 名 → [`DataSource`]
72#[derive(Debug, Clone, Default)]
73pub struct DataSourceConfig {
74    /// 保持声明顺序(便于确定性输出与对拍)
75    sources: Vec<(String, DataSource)>,
76}
77
78impl DataSourceConfig {
79    pub fn new() -> Self {
80        Self::default()
81    }
82
83    /// 从 JSON 解析:`{ "sources": { "<name>": "<kind>" } }`
84    ///
85    /// `null` / 无 `sources` → 空配置(等价单源 Mongo)。
86    pub fn from_json(v: &Value) -> Result<Self, String> {
87        let mut cfg = Self::new();
88        let Some(obj) = v.as_object() else {
89            return Ok(cfg);
90        };
91        if let Some(Value::Object(sources)) = obj.get("sources") {
92            for (name, kind) in sources {
93                let kind = kind
94                    .as_str()
95                    .ok_or_else(|| format!("数据源 {} 的 kind 必须是字符串", name))?;
96                cfg.register(name, DataSource::from_kind(kind)?);
97            }
98        }
99        Ok(cfg)
100    }
101
102    /// 注册 / 覆盖一个数据源
103    pub fn register(&mut self, name: &str, source: DataSource) {
104        if let Some(slot) = self.sources.iter_mut().find(|(n, _)| n == name) {
105            slot.1 = source;
106        } else {
107            self.sources.push((name.to_string(), source));
108        }
109    }
110
111    pub fn get(&self, name: &str) -> Option<DataSource> {
112        self.sources
113            .iter()
114            .find(|(n, _)| n == name)
115            .map(|(_, s)| *s)
116    }
117
118    /// 解析 schema 声明的 source 名 → [`DataSource`]
119    ///
120    /// - `None`(schema 未声明)→ 名为 [`DEFAULT_SOURCE`] 的数据源;配置未声明则回落 Mongo
121    /// - `Some(name)` → 必须已在配置中声明,否则 Err(避免静默走错库)
122    pub fn resolve(&self, source: Option<&str>) -> Result<DataSource, String> {
123        match source {
124            None => Ok(self.get(DEFAULT_SOURCE).unwrap_or(DataSource::Mongo)),
125            Some(name) => self
126                .get(name)
127                .ok_or_else(|| format!("schema 绑定的数据源未在配置中声明: {}", name)),
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use serde_json::json;
136
137    #[test]
138    fn parse_and_resolve() {
139        let cfg = DataSourceConfig::from_json(&json!({
140            "sources": { "default": "mongo", "mysql1": "mysql", "pg1": "postgres", "lite": "sqlite" }
141        }))
142        .unwrap();
143
144        assert_eq!(cfg.resolve(None).unwrap(), DataSource::Mongo);
145        assert_eq!(
146            cfg.resolve(Some("mysql1")).unwrap(),
147            DataSource::Sql(Backend::Mysql)
148        );
149        assert_eq!(
150            cfg.resolve(Some("pg1")).unwrap(),
151            DataSource::Sql(Backend::Postgres)
152        );
153        assert_eq!(
154            cfg.resolve(Some("lite")).unwrap(),
155            DataSource::Sql(Backend::Sqlite)
156        );
157    }
158
159    #[test]
160    fn empty_config_defaults_to_mongo() {
161        let cfg = DataSourceConfig::from_json(&Value::Null).unwrap();
162        assert_eq!(cfg.resolve(None).unwrap(), DataSource::Mongo);
163        // 未声明 default 时,显式 source 名仍报错
164        assert!(cfg.resolve(Some("missing")).is_err());
165    }
166
167    #[test]
168    fn unknown_kind_and_source_error() {
169        assert!(DataSourceConfig::from_json(&json!({ "sources": { "x": "oracle" } })).is_err());
170        let cfg =
171            DataSourceConfig::from_json(&json!({ "sources": { "default": "mongo" } })).unwrap();
172        assert!(cfg.resolve(Some("nope")).is_err());
173    }
174
175    #[test]
176    fn kind_roundtrip() {
177        assert_eq!(DataSource::from_kind("mongodb").unwrap().as_str(), "mongo");
178        assert_eq!(DataSource::from_kind("MySQL").unwrap().as_str(), "mysql");
179        assert_eq!(
180            DataSource::from_kind("pg").unwrap().backend(),
181            Some(Backend::Postgres)
182        );
183        assert!(DataSource::Mongo.is_mongo());
184    }
185
186    #[test]
187    fn local_kind_roundtrip() {
188        let ds = DataSource::from_kind("local").unwrap();
189        assert_eq!(ds, DataSource::Local);
190        assert_eq!(ds.as_str(), "local");
191        assert!(ds.backend().is_none());
192        assert!(ds.is_local());
193        assert!(!DataSource::Mongo.is_local());
194        // 未知 kind 仍报错
195        assert!(DataSource::from_kind("oracle").is_err());
196    }
197}