Skip to main content

dhttp_home/identity/
settings.rs

1use std::path::{Path, PathBuf};
2#[cfg(feature = "ssl")]
3use std::{fmt::Display, ops::ControlFlow};
4
5use serde::{Deserialize, Serialize};
6use snafu::{ResultExt, Snafu};
7use tokio::fs;
8use toml::Spanned;
9
10use dhttp_identity::name::DhttpName;
11
12use crate::DhttpHome;
13
14#[derive(Default, Debug, Clone, Serialize, Deserialize)]
15pub struct DefaultSection {
16    pub name: Option<Spanned<DhttpName<'static>>>,
17}
18
19#[derive(Default, Debug, Clone, Serialize, Deserialize)]
20pub struct DhttpSettings {
21    #[serde(default)]
22    pub default: DefaultSection,
23}
24
25impl DhttpSettings {
26    pub const FILE_NAME: &'static str = "settings.toml";
27
28    pub fn default_identity_name(&self) -> Option<&DhttpName<'static>> {
29        self.default.name.as_ref().map(|s| s.as_ref())
30    }
31
32    pub fn set_default_identity_name(&mut self, name: DhttpName<'static>) {
33        let span = match &self.default.name {
34            Some(spanned) => spanned.span(),
35            None => 0..0,
36        };
37        self.default.name = Some(Spanned::new(span, name));
38    }
39}
40
41#[derive(Debug)]
42pub struct DhttpSettingsFile {
43    path: PathBuf,
44    #[allow(dead_code)]
45    content: Option<String>,
46    settings: DhttpSettings,
47}
48
49#[cfg(feature = "ssl")]
50#[derive(Debug, Clone, Copy)]
51pub(crate) struct LineCol {
52    line: usize,
53    column: usize,
54}
55
56#[cfg(feature = "ssl")]
57impl LineCol {
58    fn locate(source: &str, offset: usize) -> LineCol {
59        let fold = |last: LineCol, (index, char)| {
60            let current = match char {
61                '\n' => LineCol {
62                    line: last.line + 1,
63                    column: 1,
64                },
65                _ => LineCol {
66                    line: last.line,
67                    column: last.column + 1,
68                },
69            };
70            if index == offset {
71                ControlFlow::Break(current)
72            } else {
73                ControlFlow::Continue(current)
74            }
75        };
76        let (ControlFlow::Continue(line_col) | ControlFlow::Break(line_col)) =
77            (source.chars().enumerate()).try_fold(LineCol { line: 1, column: 1 }, fold);
78        line_col
79    }
80}
81
82#[cfg(feature = "ssl")]
83impl Display for LineCol {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(f, "{}:{}", self.line, self.column)
86    }
87}
88
89#[cfg(feature = "ssl")]
90#[derive(Debug)]
91pub(crate) struct FileLineCol {
92    path: PathBuf,
93    line_col: LineCol,
94}
95
96#[cfg(feature = "ssl")]
97impl Display for FileLineCol {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        write!(f, "{}:{}", self.path.display(), self.line_col)
100    }
101}
102
103#[derive(Snafu, Debug)]
104#[snafu(module)]
105pub enum LoadDhttpSettingsError {
106    #[snafu(display("failed to read settings file {}", path.display()))]
107    Io {
108        path: PathBuf,
109        source: std::io::Error,
110    },
111    #[snafu(display("failed to deserialize settings file {}", path.display()))]
112    Deserialize {
113        path: PathBuf,
114        source: toml::de::Error,
115    },
116}
117
118#[derive(Snafu, Debug)]
119#[snafu(module)]
120pub enum SaveDhttpSettingsError {
121    Serialize {
122        path: PathBuf,
123        source: toml::ser::Error,
124    },
125    Io {
126        path: PathBuf,
127        source: std::io::Error,
128    },
129}
130
131impl DhttpSettingsFile {
132    pub fn new(path: PathBuf) -> Self {
133        Self {
134            path,
135            content: None,
136            settings: DhttpSettings::default(),
137        }
138    }
139
140    pub fn path(&self) -> &Path {
141        &self.path
142    }
143
144    pub async fn load(path: PathBuf) -> Result<Self, LoadDhttpSettingsError> {
145        let source = fs::read_to_string(&path)
146            .await
147            .context(load_dhttp_settings_error::IoSnafu { path: &path })?;
148        let settings: DhttpSettings = toml::from_str(&source)
149            .context(load_dhttp_settings_error::DeserializeSnafu { path: &path })?;
150        Ok(Self {
151            path,
152            content: Some(source),
153            settings,
154        })
155    }
156
157    pub fn settings(&self) -> &DhttpSettings {
158        &self.settings
159    }
160
161    pub fn settings_mut(&mut self) -> &mut DhttpSettings {
162        &mut self.settings
163    }
164
165    #[cfg(feature = "ssl")]
166    pub(crate) fn locate(&self, offset: usize) -> Option<FileLineCol> {
167        let line_col = LineCol::locate(self.content.as_ref()?, offset);
168        let path = self.path.clone();
169        Some(FileLineCol { path, line_col })
170    }
171
172    pub async fn save(&self) -> Result<(), SaveDhttpSettingsError> {
173        let source = toml::to_string_pretty(&self.settings)
174            .context(save_dhttp_settings_error::SerializeSnafu { path: &self.path })?;
175        fs::write(&self.path, source)
176            .await
177            .context(save_dhttp_settings_error::IoSnafu { path: &self.path })?;
178        Ok(())
179    }
180}
181
182impl DhttpHome {
183    pub fn settings_path(&self) -> PathBuf {
184        self.join(DhttpSettings::FILE_NAME)
185    }
186
187    pub async fn load_settings(&self) -> Result<DhttpSettingsFile, LoadDhttpSettingsError> {
188        DhttpSettingsFile::load(self.settings_path()).await
189    }
190
191    pub fn new_settings(&self) -> DhttpSettingsFile {
192        DhttpSettingsFile::new(self.settings_path())
193    }
194}