swim_core/
settings.rs

1use crate::{Error, Result};
2use std::{fs::File, io::Read, path::PathBuf};
3
4use serde::Deserialize;
5
6/// The `CoreSettings` struct holds basic, yet essential information about the application.
7#[derive(Debug, Clone, Deserialize)]
8#[serde(default)]
9pub struct CoreSettings {
10    pub name: String,
11    pub secret_key: String,
12    pub language_code: String,
13    pub timezone: String,
14}
15
16impl Default for CoreSettings {
17    fn default() -> Self {
18        Self {
19            name: "DEFAULT_NAME".to_string(),
20            secret_key: "DEFAULT_SECRET_KEY".to_string(),
21            language_code: "en-us".to_string(),
22            timezone: "UTC".to_string(),
23        }
24    }
25}
26
27/// The `DatabaseSettings` struct holds information about the database connection.
28#[derive(Debug, Clone, Deserialize)]
29pub struct DatabaseSettings {
30    pub url: String,
31
32    #[serde(rename = "type")]
33    pub type_: String,
34}
35
36/// The `Settings` struct is used to configure the application.
37///
38/// It is created using the [SettingsBuilder] struct, which is returned by the `Settings::builder()` method.
39#[derive(Debug, Clone, Deserialize)]
40pub struct Settings {
41    #[serde(default)]
42    pub core: CoreSettings,
43    pub database: DatabaseSettings,
44}
45
46impl Settings {
47    pub fn builder() -> SettingsBuilder {
48        SettingsBuilder::new()
49    }
50}
51
52/// The `SettingsBuilder` struct is used to build the `Settings` struct.
53///
54/// It is created using the [Settings::builder()] method. Or alternatively, by loading the settings from a [ron](https://github.com/ron-rs/ron) file.
55///
56/// # Examples
57///
58/// ```rust
59/// # use swim_core::settings::{Settings, CoreSettings, DatabaseSettings};
60///
61/// let settings = Settings::builder()
62///     .core(CoreSettings {
63///         name: "My App".to_string(),
64///         secret_key: "My Secret Key".to_string(),
65///         ..Default::default()
66///     })
67///     .database(DatabaseSettings {
68///         url: "sqlite://my_database.db".to_string(),
69///         type_: "sqlite".to_string(),
70///     })
71///     .build();
72/// ```
73///
74/// ```no_run
75/// use swim_core::settings::Settings;
76///
77/// let settings = Settings::builder()
78///    .extend_ron(concat!(env!("CARGO_MANIFEST_DIR"), "/settings.ron"))
79///    .build();
80/// ```
81pub struct SettingsBuilder {
82    core: Option<CoreSettings>,
83    database: Option<DatabaseSettings>,
84    ron_path: Option<PathBuf>,
85}
86
87impl SettingsBuilder {
88    /// Creates a new `SettingsBuilder` instance.
89    pub fn new() -> Self {
90        Self {
91            core: None,
92            database: None,
93            ron_path: None,
94        }
95    }
96
97    /// Mutates the `core` field of the `SettingsBuilder` instance.
98    pub fn core(mut self, core: CoreSettings) -> Self {
99        self.core = Some(core);
100        self
101    }
102
103    /// Mutates the `database` field of the `SettingsBuilder` instance.
104    pub fn database(mut self, database: DatabaseSettings) -> Self {
105        self.database = Some(database);
106        self
107    }
108
109    fn load_ron(mut self, path: PathBuf) -> Result<Self> {
110        // Read the file
111        let mut file = File::open(path).expect("Failed to open settings file");
112        let mut contents = String::new();
113        file.read_to_string(&mut contents)
114            .map_err(|_| Error::RonRead)?;
115
116        // Parse the file
117        let settings: Settings = ron::from_str(&contents).expect("Failed to parse settings file");
118
119        //  Set the fields
120        self.core = Some(settings.core);
121        self.database = Some(settings.database);
122
123        Ok(self)
124    }
125
126    /// Loads the settings from a `.ron` file.
127    pub fn extend_ron<P: Into<PathBuf> + std::fmt::Debug>(mut self, path: P) -> Self {
128        self.ron_path = Some(path.into());
129
130        self
131    }
132
133    /// Builds the `Settings` instance.
134    pub fn build(mut self) -> Result<Settings> {
135        if let Some(path) = self.ron_path.to_owned() {
136            self = self.load_ron(path)?;
137        }
138
139        let core = self.core.expect("Core settings are required");
140        let database = self.database.expect("Database settings are required");
141
142        if core.secret_key == "DEFAULT_SECRET_KEY" {
143            panic!("You must set a secret key");
144        }
145
146        Ok(Settings { core, database })
147    }
148}