coven_foundation/files/
settings.rs1use std::path::Path;
4
5use serde::{Deserialize, Serialize};
6
7use crate::files::atomic_file::{self, FileError};
8use crate::id_source::{DeviceId, StoreId};
9
10pub(crate) const SETTINGS_FILE: &str = "settings.json";
11
12#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct StoreSettings {
17 pub id: StoreId,
19 pub device_id: DeviceId,
21}
22
23#[derive(Debug, thiserror::Error)]
25pub enum SettingsError {
26 #[error("settings are missing for store {0}")]
28 Missing(StoreId),
29 #[error("invalid store settings: {0}")]
31 Corrupt(#[source] Box<dyn std::error::Error + Send + Sync>),
32 #[error("settings identify store {actual}, directory identifies {expected}")]
34 WrongStore {
35 expected: StoreId,
37 actual: StoreId,
39 },
40 #[error("settings are not a regular file for store {0}")]
42 NotRegularFile(StoreId),
43 #[error("store settings: {0}")]
45 File(FileError),
46 #[error(transparent)]
49 Bug(#[from] crate::task::Bug),
50}
51
52impl From<FileError> for SettingsError {
53 fn from(error: FileError) -> Self {
54 match error {
55 FileError::Bug(bug) => Self::Bug(bug),
56 error => Self::File(error),
57 }
58 }
59}
60
61impl crate::task::CarriesBug for SettingsError {
62 fn bug(&self) -> Option<&crate::task::Bug> {
63 match self {
64 Self::Bug(bug) => Some(bug),
65 Self::File(error) => error.bug(),
66 Self::Missing(_)
67 | Self::Corrupt(_)
68 | Self::WrongStore { .. }
69 | Self::NotRegularFile(_) => None,
70 }
71 }
72}
73
74pub(crate) fn read(directory: &Path, id: StoreId) -> Result<StoreSettings, SettingsError> {
75 let path = directory.join(SETTINGS_FILE);
76 match std::fs::symlink_metadata(&path) {
77 Ok(metadata) if !metadata.is_file() => return Err(SettingsError::NotRegularFile(id)),
78 Ok(_) => {}
79 Err(source) if source.kind() == std::io::ErrorKind::NotFound => {
80 return Err(SettingsError::Missing(id))
81 }
82 Err(source) => return Err(FileError::at("inspect settings file", &path, source).into()),
83 }
84 let bytes = atomic_file::read_optional(&path)?.ok_or(SettingsError::Missing(id))?;
85 let settings: StoreSettings = serde_json::from_slice(&bytes)
86 .map_err(|source| SettingsError::Corrupt(Box::new(source)))?;
87 if settings.id != id {
88 return Err(SettingsError::WrongStore { expected: id, actual: settings.id });
89 }
90 Ok(settings)
91}
92
93pub(crate) fn write(directory: &Path, settings: &StoreSettings) -> Result<(), SettingsError> {
94 let bytes =
95 serde_json::to_vec(settings).map_err(|source| SettingsError::Corrupt(Box::new(source)))?;
96 atomic_file::replace(&directory.join(SETTINGS_FILE), &bytes)?;
97 Ok(())
98}
99
100#[cfg(test)]
101#[path = "settings_tests.rs"]
102mod tests;