stry_common/
config.rs

1//! Init config information, everything else is handled though the frontend.
2
3use crate::layered::{Anulap, Initialize};
4
5#[cfg(any(feature = "with-source-clap", feature = "with-source-on"))]
6use crate::layered::Source;
7
8/// A config layer source that pulls values from a clap `ArgMatches`.
9#[cfg(feature = "with-source-clap")]
10#[derive(Debug)]
11pub struct ClapSource<'a> {
12    matches: clap::ArgMatches<'a>,
13}
14
15#[cfg(feature = "with-source-clap")]
16impl<'a> ClapSource<'a> {
17    /// Create a new clap source.
18    pub fn new(matches: clap::ArgMatches<'a>) -> Self {
19        Self { matches }
20    }
21}
22
23#[cfg(feature = "with-source-clap")]
24impl<'a> Source for ClapSource<'a> {
25    fn get(&self, key: &str) -> Option<String> {
26        self.matches.value_of(key).map(String::from)
27    }
28}
29
30/// A config layer source based on a RON file.
31#[cfg(feature = "with-source-ron")]
32#[derive(Debug)]
33pub struct RonSource {
34    value: ron::Value,
35}
36
37#[cfg(feature = "with-source-ron")]
38impl RonSource {
39    /// Create a new source using the data from a file.
40    pub fn from_file<P>(path: P) -> Result<Self, RonSourceError>
41    where
42        P: AsRef<std::path::Path>,
43    {
44        let file = std::fs::OpenOptions::new().read(true).open(path)?;
45        let mut reader = std::io::BufReader::new(file);
46
47        let value = ron::de::from_reader(&mut reader)?;
48
49        Ok(Self { value })
50    }
51}
52
53#[cfg(feature = "with-source-ron")]
54impl Source for RonSource {
55    fn get(&self, key: &str) -> Option<String> {
56        match &self.value {
57            ron::Value::Map(map) => map
58                .iter()
59                .find(|(k, _)| match k {
60                    ron::Value::String(k) => k == key,
61                    _ => false,
62                })
63                .and_then(|(_, value)| -> Option<String> {
64                    match value {
65                        ron::Value::Bool(boolean) => Some(boolean.to_string()),
66                        ron::Value::Number(number) => match number {
67                            ron::Number::Integer(integer) => Some(integer.to_string()),
68                            ron::Number::Float(float) => Some(float.get().to_string()),
69                        },
70                        ron::Value::String(string) => Some(string.clone()),
71                        _ => None,
72                    }
73                }),
74            _ => None,
75        }
76    }
77}
78
79/// Errors that could occur when created a [`RonSource`].
80#[cfg(feature = "with-source-ron")]
81#[derive(Debug)]
82pub enum RonSourceError {
83    IO { source: std::io::Error },
84    Ron { source: ron::Error },
85}
86
87#[cfg(feature = "with-source-ron")]
88impl std::fmt::Display for RonSourceError {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            RonSourceError::IO { source } => write!(f, "ron source, io error: {}", source),
92            RonSourceError::Ron { source } => {
93                write!(f, "ron source, ron deserialize error: {}", source)
94            }
95        }
96    }
97}
98
99#[cfg(feature = "with-source-ron")]
100impl std::error::Error for RonSourceError {
101    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
102        match self {
103            RonSourceError::IO { source } => Some(source),
104            RonSourceError::Ron { source } => Some(source),
105        }
106    }
107}
108
109#[cfg(feature = "with-source-ron")]
110impl From<std::io::Error> for RonSourceError {
111    fn from(source: std::io::Error) -> Self {
112        Self::IO { source }
113    }
114}
115
116#[cfg(feature = "with-source-ron")]
117impl From<ron::Error> for RonSourceError {
118    fn from(source: ron::Error) -> Self {
119        Self::Ron { source }
120    }
121}
122
123/// The application init configuration.
124#[derive(Clone, Debug, serde::Deserialize)]
125#[serde(default)]
126pub struct Config {
127    /// Defines what ip the web server will be bound to.
128    ///
129    /// # Default
130    ///
131    /// If constructed with [`Default::default`] this value is set to `[0, 0, 0, 0]` (aka `0.0.0.0`).
132    pub ip: [u8; 4],
133
134    /// Defines what port the web server will listen to.
135    ///
136    /// # Default
137    ///
138    /// If constructed with [`Default::default`] this value is set to `8901`.
139    pub port: u16,
140
141    /// The database connection URI.
142    ///
143    /// Uses following format:
144    ///
145    /// ```not_rust
146    /// scheme://[username:password@]host[:port1][,...hostN[:portN]][/[database][?options]]
147    /// ```
148    ///
149    /// # Default
150    ///
151    /// If constructed with [`Default::default`] this value is set to `postgres://stry:stry@localhost:5432/stry`.
152    ///
153    /// # Examples
154    ///
155    /// Connecting to `PostgreSQL`:
156    ///
157    /// ```not_rust
158    /// postgres://stry:stry@localhost:5432/stry
159    /// ```
160    ///
161    /// Connecting with `SQLite`:
162    ///
163    /// ```not_rust
164    /// sqlite://stry.db
165    /// ```
166    ///
167    /// # Warning
168    ///
169    /// The parser for this is very simple and may not be able to understand
170    /// every valid URI.
171    pub database: String,
172}
173
174impl Initialize for Config {
175    fn init(config: &Anulap<'_>) -> Option<Self> {
176        Some(Self {
177            ip: config
178                .get("ip")
179                .and_then(|value| {
180                    let mut parts = value
181                        .split('.')
182                        .map(str::parse)
183                        .collect::<Vec<Result<u8, _>>>();
184
185                    let four = parts.pop()?.ok()?;
186                    let three = parts.pop()?.ok()?;
187                    let two = parts.pop()?.ok()?;
188                    let one = parts.pop()?.ok()?;
189
190                    Some([one, two, three, four])
191                })
192                .unwrap_or_else(|| [0, 0, 0, 0]),
193            port: config
194                .get("port")
195                .and_then(|value| value.parse().ok())
196                .unwrap_or(8901),
197            database: config
198                .get("database")
199                .unwrap_or_else(|| String::from("postgres://stry:stry@localhost:5432/stry")),
200        })
201    }
202}
203
204impl Default for Config {
205    fn default() -> Self {
206        Self {
207            ip: [0, 0, 0, 0],
208            port: 8901,
209            database: String::from("postgres://stry:stry@localhost:5432/stry"),
210        }
211    }
212}