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