/
mcmare
/
RustAPI
Обзор
Документация
Войти
/
mcmare
/
RustAPI
Код
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
engine/src/config/connections.rs
116 строк
4 KB
mcmare
Wire up server binary, sample config, and README
18 июл 2026, 16:41
18 июл 2026, 16:41
f181395
Код
Авторство
О чём код?
use anyhow::{bail, Context, Result}; use serde::Deserialize; use std::{collections::HashMap, path::Path}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DbDriver { Postgres, MySql, Sqlite, } impl DbDriver { fn parse(raw: &str) -> Result<Self, String> { match raw { "postgres" => Ok(DbDriver::Postgres), "mysql" => Ok(DbDriver::MySql), "sqlite" => Ok(DbDriver::Sqlite), other => Err(format!( "unknown driver '{other}' (expected one of: postgres, mysql, sqlite)" )), } } } #[derive(Debug, Clone)] pub struct ConnectionDef { pub driver: DbDriver, /// Connection URL with any `${ENV_VAR}` placeholders already resolved. pub url: String, } #[derive(Debug, Deserialize)] struct RawConnectionsFile { #[serde(default)] connections: HashMap<String, RawConnection>, } #[derive(Debug, Deserialize)] struct RawConnection { driver: String, url: String, } /// Loads `connections.yaml`. Missing file is not an error — it just means no DB /// connections are configured (a scripts-only deployment, for instance). pub fn load_connections(path: &Path) -> Result<HashMap<String, ConnectionDef>> { if !path.exists() { return Ok(HashMap::new()); } let raw_text = std::fs::read_to_string(path) .with_context(|| format!("failed to read {}", path.display()))?; let raw: RawConnectionsFile = serde_yaml::from_str(&raw_text) .with_context(|| format!("failed to parse {}", path.display()))?; let mut connections = HashMap::with_capacity(raw.connections.len()); for (name, conn) in raw.connections { let driver = DbDriver::parse(&conn.driver).map_err(|msg| { anyhow::anyhow!( "{}: connection '{name}': {msg}", path.display() ) })?; let url = resolve_env_placeholders(&conn.url).with_context(|| { format!("{}: connection '{name}': field 'url'", path.display()) })?; connections.insert(name, ConnectionDef { driver, url }); } Ok(connections) } /// Substitutes every `${VAR_NAME}` occurrence in `input` with the value of the /// matching environment variable. Errors out (naming the variable) if any referenced /// variable is not set, so secrets never silently fall back to an empty string. pub(crate) fn resolve_env_placeholders(input: &str) -> Result<String> { let mut out = String::with_capacity(input.len()); let mut rest = input; while let Some(start) = rest.find("${") { let Some(end) = rest[start..].find('}') else { bail!("unterminated '${{' placeholder in '{input}'"); }; let end = start + end; out.push_str(&rest[..start]); let var_name = &rest[start + 2..end]; let value = std::env::var(var_name) .with_context(|| format!("environment variable '{var_name}' is not set"))?; out.push_str(&value); rest = &rest[end + 1..]; } out.push_str(rest); Ok(out) } #[cfg(test)] mod tests { use super::*; #[test] fn substitutes_env_var() { std::env::set_var("RUSTAPI_TEST_VAR", "secret"); assert_eq!( resolve_env_placeholders("postgres://${RUSTAPI_TEST_VAR}@host").unwrap(), "postgres://secret@host" ); } #[test] fn errors_on_missing_env_var() { std::env::remove_var("RUSTAPI_TEST_VAR_MISSING"); assert!(resolve_env_placeholders("${RUSTAPI_TEST_VAR_MISSING}").is_err()); } #[test] fn passes_through_plain_string() { assert_eq!(resolve_env_placeholders("sqlite://data.db").unwrap(), "sqlite://data.db"); } }