Skip to main content

openauth_cli/
config.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6use toml_edit::{value, Array, DocumentMut};
7
8#[derive(Debug, Error)]
9pub enum ConfigError {
10    #[error("failed to read {path}: {source}")]
11    Read {
12        path: PathBuf,
13        source: std::io::Error,
14    },
15    #[error("failed to write {path}: {source}")]
16    Write {
17        path: PathBuf,
18        source: std::io::Error,
19    },
20    #[error("failed to parse openauth.toml: {0}")]
21    ParseToml(#[from] toml_edit::de::Error),
22    #[error("failed to parse openauth.toml document: {0}")]
23    ParseDocument(#[from] toml_edit::TomlError),
24    #[error("failed to render openauth.toml: {0}")]
25    SerializeToml(#[from] toml_edit::ser::Error),
26    #[error("plugins.enabled must be an array")]
27    InvalidPlugins,
28}
29
30#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(default)]
32pub struct CliConfig {
33    pub project: ProjectConfig,
34    pub database: DatabaseConfig,
35    pub security: SecurityConfig,
36    pub plugins: PluginsConfig,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(default)]
41pub struct ProjectConfig {
42    pub framework: Option<String>,
43    pub base_url: String,
44    pub base_path: String,
45    pub production: bool,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(default)]
50pub struct DatabaseConfig {
51    pub adapter: String,
52    pub provider: Option<String>,
53    pub url_env: String,
54    pub migrations_dir: String,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(default)]
59pub struct SecurityConfig {
60    pub secret_env: String,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
64#[serde(default)]
65pub struct PluginsConfig {
66    pub enabled: Vec<String>,
67}
68
69impl Default for ProjectConfig {
70    fn default() -> Self {
71        Self {
72            framework: None,
73            base_url: "http://localhost:3000/api/auth".to_owned(),
74            base_path: "/api/auth".to_owned(),
75            production: false,
76        }
77    }
78}
79
80impl Default for DatabaseConfig {
81    fn default() -> Self {
82        Self {
83            adapter: "sqlx".to_owned(),
84            provider: None,
85            url_env: "DATABASE_URL".to_owned(),
86            migrations_dir: "migrations/openauth".to_owned(),
87        }
88    }
89}
90
91impl Default for SecurityConfig {
92    fn default() -> Self {
93        Self {
94            secret_env: "OPENAUTH_SECRET".to_owned(),
95        }
96    }
97}
98
99impl CliConfig {
100    pub fn parse_str(source: &str) -> Result<Self, ConfigError> {
101        source.parse()
102    }
103
104    pub fn load(path: &Path) -> Result<Self, ConfigError> {
105        let source = fs::read_to_string(path).map_err(|source| ConfigError::Read {
106            path: path.to_path_buf(),
107            source,
108        })?;
109        Self::parse_str(&source)
110    }
111
112    pub fn load_optional(path: &Path) -> Result<Option<Self>, ConfigError> {
113        if !path.exists() {
114            return Ok(None);
115        }
116        Self::load(path).map(Some)
117    }
118
119    pub fn to_toml_string(&self) -> Result<String, ConfigError> {
120        Ok(toml_edit::ser::to_string_pretty(self)?)
121    }
122
123    pub fn write(&self, path: &Path) -> Result<(), ConfigError> {
124        let rendered = self.to_toml_string()?;
125        fs::write(path, rendered).map_err(|source| ConfigError::Write {
126            path: path.to_path_buf(),
127            source,
128        })
129    }
130
131    pub fn add_plugin_to_document(source: &str, plugin: &str) -> Result<String, ConfigError> {
132        let mut document = source.parse::<DocumentMut>()?;
133        ensure_plugin_array(&mut document)?;
134        let enabled = document["plugins"]["enabled"]
135            .as_array_mut()
136            .ok_or(ConfigError::InvalidPlugins)?;
137        if !enabled.iter().any(|item| item.as_str() == Some(plugin)) {
138            enabled.push(plugin);
139        }
140        Ok(document.to_string())
141    }
142
143    pub fn remove_plugin_from_document(source: &str, plugin: &str) -> Result<String, ConfigError> {
144        let mut document = source.parse::<DocumentMut>()?;
145        ensure_plugin_array(&mut document)?;
146        let enabled = document["plugins"]["enabled"]
147            .as_array_mut()
148            .ok_or(ConfigError::InvalidPlugins)?;
149        enabled.retain(|item| item.as_str() != Some(plugin));
150        Ok(document.to_string())
151    }
152}
153
154impl std::str::FromStr for CliConfig {
155    type Err = ConfigError;
156
157    fn from_str(source: &str) -> Result<Self, Self::Err> {
158        Ok(toml_edit::de::from_str(source)?)
159    }
160}
161
162fn ensure_plugin_array(document: &mut DocumentMut) -> Result<(), ConfigError> {
163    if document["plugins"].is_none() {
164        document["plugins"] = toml_edit::table();
165    }
166    if document["plugins"]["enabled"].is_none() {
167        document["plugins"]["enabled"] = value(Array::default());
168    }
169    if !document["plugins"]["enabled"].is_array() {
170        return Err(ConfigError::InvalidPlugins);
171    }
172    Ok(())
173}