use serde_json::Value;
use crate::dialect::Backend;
pub const DEFAULT_SOURCE: &str = "default";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataSource {
Mongo,
Sql(Backend),
}
impl DataSource {
pub fn from_kind(kind: &str) -> Result<DataSource, String> {
match kind.to_lowercase().as_str() {
"mongo" | "mongodb" => Ok(DataSource::Mongo),
other => Backend::parse(other).map(DataSource::Sql),
}
}
pub fn as_str(&self) -> &'static str {
match self {
DataSource::Mongo => "mongo",
DataSource::Sql(b) => b.as_str(),
}
}
pub fn is_mongo(&self) -> bool {
matches!(self, DataSource::Mongo)
}
pub fn backend(&self) -> Option<Backend> {
match self {
DataSource::Sql(b) => Some(*b),
DataSource::Mongo => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct DataSourceConfig {
sources: Vec<(String, DataSource)>,
}
impl DataSourceConfig {
pub fn new() -> Self {
Self::default()
}
pub fn from_json(v: &Value) -> Result<Self, String> {
let mut cfg = Self::new();
let Some(obj) = v.as_object() else {
return Ok(cfg);
};
if let Some(Value::Object(sources)) = obj.get("sources") {
for (name, kind) in sources {
let kind = kind
.as_str()
.ok_or_else(|| format!("数据源 {} 的 kind 必须是字符串", name))?;
cfg.register(name, DataSource::from_kind(kind)?);
}
}
Ok(cfg)
}
pub fn register(&mut self, name: &str, source: DataSource) {
if let Some(slot) = self.sources.iter_mut().find(|(n, _)| n == name) {
slot.1 = source;
} else {
self.sources.push((name.to_string(), source));
}
}
pub fn get(&self, name: &str) -> Option<DataSource> {
self.sources
.iter()
.find(|(n, _)| n == name)
.map(|(_, s)| *s)
}
pub fn resolve(&self, source: Option<&str>) -> Result<DataSource, String> {
match source {
None => Ok(self.get(DEFAULT_SOURCE).unwrap_or(DataSource::Mongo)),
Some(name) => self
.get(name)
.ok_or_else(|| format!("schema 绑定的数据源未在配置中声明: {}", name)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parse_and_resolve() {
let cfg = DataSourceConfig::from_json(&json!({
"sources": { "default": "mongo", "mysql1": "mysql", "pg1": "postgres", "lite": "sqlite" }
}))
.unwrap();
assert_eq!(cfg.resolve(None).unwrap(), DataSource::Mongo);
assert_eq!(
cfg.resolve(Some("mysql1")).unwrap(),
DataSource::Sql(Backend::Mysql)
);
assert_eq!(
cfg.resolve(Some("pg1")).unwrap(),
DataSource::Sql(Backend::Postgres)
);
assert_eq!(
cfg.resolve(Some("lite")).unwrap(),
DataSource::Sql(Backend::Sqlite)
);
}
#[test]
fn empty_config_defaults_to_mongo() {
let cfg = DataSourceConfig::from_json(&Value::Null).unwrap();
assert_eq!(cfg.resolve(None).unwrap(), DataSource::Mongo);
assert!(cfg.resolve(Some("missing")).is_err());
}
#[test]
fn unknown_kind_and_source_error() {
assert!(DataSourceConfig::from_json(&json!({ "sources": { "x": "oracle" } })).is_err());
let cfg =
DataSourceConfig::from_json(&json!({ "sources": { "default": "mongo" } })).unwrap();
assert!(cfg.resolve(Some("nope")).is_err());
}
#[test]
fn kind_roundtrip() {
assert_eq!(DataSource::from_kind("mongodb").unwrap().as_str(), "mongo");
assert_eq!(DataSource::from_kind("MySQL").unwrap().as_str(), "mysql");
assert_eq!(
DataSource::from_kind("pg").unwrap().backend(),
Some(Backend::Postgres)
);
assert!(DataSource::Mongo.is_mongo());
}
}