dhttp_home/identity/
settings.rs1use 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}