rill_config/
lib.rs

1pub mod env;
2
3use anyhow::Error;
4use async_trait::async_trait;
5pub use env::FromEnv;
6use meio::LiteTask;
7use serde::{de::DeserializeOwned, Serialize};
8use std::fmt::Debug;
9use std::marker::PhantomData;
10use std::path::PathBuf;
11use tokio::fs::{remove_file, File};
12use tokio::io::{AsyncReadExt, AsyncWriteExt};
13
14pub trait Config: Debug + Send + 'static {}
15
16#[async_trait]
17pub trait ReadableConfig: Config + DeserializeOwned {
18    async fn read(path: PathBuf) -> Result<Self, Error> {
19        let mut file = File::open(path).await?;
20        let mut contents = Vec::new();
21        file.read_to_end(&mut contents).await?;
22        Self::parse(&contents)
23    }
24
25    fn parse(data: &[u8]) -> Result<Self, Error> {
26        toml::from_slice(data).map_err(Error::from)
27    }
28}
29
30pub struct ReadConfigFile<T> {
31    path: Option<PathBuf>,
32    default_path: &'static str,
33    _config: PhantomData<T>,
34}
35
36impl<T> ReadConfigFile<T> {
37    pub fn new(path: Option<PathBuf>, default_path: &'static str) -> Self {
38        Self {
39            path,
40            default_path,
41            _config: PhantomData,
42        }
43    }
44}
45
46#[async_trait]
47impl<T: ReadableConfig> LiteTask for ReadConfigFile<T> {
48    type Output = Option<T>;
49
50    async fn interruptable_routine(mut self) -> Result<Self::Output, Error> {
51        let config = {
52            if let Some(path) = self.path {
53                Some(T::read(path).await?)
54            } else {
55                let path = self.default_path.into();
56                T::read(path).await.ok()
57            }
58        };
59        log::trace!("Config ready: {:?}", config);
60        Ok(config)
61    }
62}
63
64#[async_trait]
65pub trait WritableConfig: Config + Serialize + Sync {
66    async fn write(&self, path: PathBuf) -> Result<(), Error> {
67        let mut file = File::create(path).await?;
68        let data = toml::to_vec(self)?;
69        file.write_all(&data).await?;
70        Ok(())
71    }
72
73    async fn drop_file(path: PathBuf) -> Result<(), Error> {
74        remove_file(path).await?;
75        Ok(())
76    }
77}
78
79pub struct WriteConfigFile<T> {
80    path: PathBuf,
81    config: T,
82}
83
84impl<T> WriteConfigFile<T> {
85    pub fn new(path: PathBuf, config: T) -> Self {
86        Self { path, config }
87    }
88}
89
90#[async_trait]
91impl<T: WritableConfig> LiteTask for WriteConfigFile<T> {
92    type Output = ();
93
94    async fn interruptable_routine(mut self) -> Result<Self::Output, Error> {
95        log::trace!("Storing config to {:?}: {:?}", self.path, self.config);
96        self.config.write(self.path).await?;
97        Ok(())
98    }
99}
100
101pub struct DropConfigFile<T> {
102    path: PathBuf,
103    _config: PhantomData<T>,
104}
105
106impl<T> DropConfigFile<T> {
107    pub fn new(path: PathBuf) -> Self {
108        Self {
109            path,
110            _config: PhantomData,
111        }
112    }
113}
114
115#[async_trait]
116impl<T: WritableConfig> LiteTask for DropConfigFile<T> {
117    type Output = ();
118
119    async fn interruptable_routine(mut self) -> Result<Self::Output, Error> {
120        log::trace!("Drop config file: {:?}", self.path);
121        T::drop_file(self.path).await?;
122        Ok(())
123    }
124}