sntl-schema 0.1.0

Shared schema analysis and cache library for Sentinel ORM macros and CLI
Documentation
use crate::error::{Error, Result};
use serde::Deserialize;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct Config {
    pub database: DatabaseConfig,
    pub offline: OfflineConfig,
    pub schema: SchemaConfig,
    pub macros: MacrosConfig,
    pub cache: CacheConfig,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct DatabaseConfig {
    pub url: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum OfflineMode {
    On,
    #[default]
    Off,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct OfflineConfig {
    #[serde(deserialize_with = "deserialize_offline_flag")]
    pub enabled: OfflineMode,
}

fn deserialize_offline_flag<'de, D>(d: D) -> std::result::Result<OfflineMode, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let v: toml::Value = toml::Value::deserialize(d)?;
    Ok(match v {
        toml::Value::Boolean(true) => OfflineMode::On,
        toml::Value::Boolean(false) => OfflineMode::Off,
        toml::Value::String(s) if s == "on" || s == "true" => OfflineMode::On,
        _ => OfflineMode::Off,
    })
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct SchemaConfig {
    pub path: String,
    pub dialect: String,
}

impl Default for SchemaConfig {
    fn default() -> Self {
        Self {
            path: ".sentinel/schema.toml".into(),
            dialect: "postgres-16".into(),
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct MacrosConfig {
    pub strict_nullable: bool,
    pub deny_warnings: bool,
}

impl Default for MacrosConfig {
    fn default() -> Self {
        Self {
            strict_nullable: true,
            deny_warnings: false,
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct CacheConfig {
    pub dir: String,
}

impl Default for CacheConfig {
    fn default() -> Self {
        Self {
            dir: ".sentinel".into(),
        }
    }
}

impl Config {
    pub fn load_from(path: impl AsRef<Path>) -> Result<Self> {
        let path = path.as_ref();
        let mut cfg: Config = match std::fs::read_to_string(path) {
            Ok(text) => toml::from_str(&text).map_err(|source| Error::TomlParse {
                path: path.to_path_buf(),
                source,
            })?,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Config::default(),
            Err(source) => {
                return Err(Error::Io {
                    path: path.to_path_buf(),
                    source,
                });
            }
        };
        cfg.apply_env();
        Ok(cfg)
    }

    fn apply_env(&mut self) {
        if let Ok(url) = std::env::var("SENTINEL_DATABASE_URL") {
            self.database.url = Some(url);
        }
        match std::env::var("SENTINEL_OFFLINE").as_deref() {
            Ok("true") | Ok("1") | Ok("on") => self.offline.enabled = OfflineMode::On,
            Ok("false") | Ok("0") | Ok("off") => self.offline.enabled = OfflineMode::Off,
            _ => {}
        }
        if let Ok(dir) = std::env::var("SENTINEL_CACHE_DIR") {
            self.cache.dir = dir;
        }
    }

    pub fn cache_dir(&self) -> PathBuf {
        PathBuf::from(&self.cache.dir)
    }
}