cloud_terrastodon_config/
config.rs1use chrono::Utc;
2use cloud_terrastodon_pathing::AppDir;
3use eyre::Result;
4use eyre::eyre;
5use serde::Deserialize;
6use serde::Serialize;
7use serde_json::Value;
8use serde_json::{self};
9use std::path::PathBuf;
10use tokio::fs;
11use tracing::debug;
12use tracing::warn;
13#[async_trait::async_trait]
14pub trait Config:
15 Sized
16 + Default
17 + std::fmt::Debug
18 + Sync
19 + for<'de> Deserialize<'de>
20 + Serialize
21 + Clone
22 + Send
23 + 'static
24 + PartialEq
25{
26 const FILE_SLUG: &'static str;
28
29 fn config_path() -> PathBuf {
31 AppDir::Config.join(format!("{}.json", Self::FILE_SLUG))
32 }
33
34 async fn load() -> Result<Self> {
36 let path = Self::config_path();
37 let instance = if path.exists() {
38 let content = fs::read_to_string(&path).await?;
39 let user_json: Value = match serde_json::from_str(&content) {
41 Ok(val) => val,
42 Err(err) => {
43 warn!(
44 "Failed to load config as valid json, will make a backup and will revert to defaults. Error: {}",
45 err
46 );
47 let now = Utc::now().format("%Y%m%dT%H%M%SZ");
49 let backup_path =
50 path.with_file_name(format!("{}-{}.json.bak", Self::FILE_SLUG, now));
51 fs::copy(&path, &backup_path).await?;
52 serde_json::to_value(Self::default())?
54 }
55 };
56
57 let default_json = serde_json::to_value(Self::default())?;
59 let merged_json = merge_json(default_json, user_json);
61 serde_json::from_value(merged_json)
63 .map_err(|e| eyre!("Failed to deserialize merged config: {}", e))?
64 } else {
65 Self::default()
66 };
67
68 Ok(instance)
69 }
70
71 async fn save(&self) -> Result<()> {
73 let path = Self::config_path();
74 if let Some(dir) = path.parent() {
75 fs::create_dir_all(dir).await?;
76 }
77 let content = serde_json::to_string_pretty(self)?;
78 debug!("Writing config to {:?}", path);
79 fs::write(&path, content).await?;
80 Ok(())
81 }
82
83 async fn modify_and_save<F>(&mut self, f: F) -> Result<()>
84 where
85 F: FnOnce(&mut Self) + Send,
86 {
87 f(self);
88 self.save().await?;
89 Ok(())
90 }
91}
92
93fn merge_json(default: Value, user: Value) -> Value {
97 match (default, user) {
98 (Value::Object(mut default_map), Value::Object(user_map)) => {
100 for (key, user_value) in user_map {
101 let entry = default_map.entry(key).or_insert(Value::Null);
102 *entry = merge_json(entry.take(), user_value);
103 }
104 Value::Object(default_map)
105 }
106 (_, user_value) => user_value,
108 }
109}