rebterlai/crates/logtail-poster/src/config.rs

120 lines
3.5 KiB
Rust

use serde::{Deserialize, Serialize};
use std::{env, ffi::OsString, fs, io, path::PathBuf};
pub(crate) fn cache_dir() -> Option<OsString> {
let dir = env::var_os("STATE_DIRECTORY").or_else(|| {
env::var_os("HOME").and_then(|dir| {
let mut dir: PathBuf = dir.into();
dir.push(".cache");
dir.push("rebterlai");
Some(OsString::from(dir))
})
});
dir
}
pub(crate) fn state_file(name: &str) -> Option<PathBuf> {
let mut dir: PathBuf = cache_dir()?.into();
dir.push(&format!("{}.json", name));
Some(dir)
}
/// The logtail configuration file. This contains cached copies of public and private
/// IDs, however in practice the most read value is the private ID.
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct Config {
private_id: logtail::PrivateID,
public_id: logtail::PublicID,
}
impl Config {
/// Constructs a brand new configuration, including a rng'd private ID and its public
/// counterpart.
pub fn new() -> Self {
let private_id = logtail::PrivateID::new();
Self {
public_id: private_id.as_public(),
private_id,
}
}
/// Returns the logtail private ID for this Config.
pub fn private_id(self) -> logtail::PrivateID {
self.private_id
}
/// Returns the logtail public ID for this Config.
pub fn public_id(self) -> logtail::PublicID {
self.public_id
}
/// Loads the relevant configuration file off disk, and creates a new one if it
/// doesn't already exist. This function is idempotent.
pub fn load<S>(collection: S) -> io::Result<Self>
where
S: Into<String>,
{
let dir = cache_dir()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "can't find directory"))?;
if let Err(_) = fs::metadata(&dir) {
fs::create_dir_all(&dir)?;
}
let fname = state_file(&collection.into()).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"can't derive logtail config filename",
)
})?;
match fs::metadata(&fname) {
Ok(_) => {
let fin = fs::File::open(&fname)?;
let cfg = serde_json::from_reader(io::BufReader::new(fin))
.or_else(|why| Err(io::Error::new(io::ErrorKind::Other, why)))?;
Ok(cfg)
}
Err(_) => {
let cfg = Self::new();
let mut fout = fs::File::create(&fname)?;
serde_json::to_writer(&mut fout, &cfg)
.or_else(|why| Err(io::Error::new(io::ErrorKind::Other, why)))?;
Ok(cfg)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempdir::TempDir;
#[test]
#[serial_test::serial]
fn cache_dir() {
let home = TempDir::new("cache").unwrap();
std::env::set_var("STATE_DIRECTORY", home.path().to_str().unwrap());
let dir = super::cache_dir();
assert!(dir.is_some());
assert_eq!(dir.unwrap(), home.path().to_str().unwrap());
home.close().unwrap();
}
#[test]
#[serial_test::serial]
fn new_and_load() {
let home = TempDir::new("cache").unwrap();
std::env::set_var("STATE_DIRECTORY", home.path().to_str().unwrap());
let cfg = Config::load("foo.bar").unwrap();
let cfg2 = Config::load("foo.bar").unwrap();
assert_eq!(cfg, cfg2);
home.close().unwrap();
}
}