Domani connects your domain to whatever you want to host on it, all with no account needed
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
domani/src/domain/store.rs

141 lines
4.3 KiB

use std::{fs, io, path};
use crate::domain;
use crate::error::unexpected::{self, Intoable, Mappable};
#[derive(thiserror::Error, Debug)]
pub enum GetError {
#[error("not found")]
NotFound,
#[error(transparent)]
Unexpected(#[from] unexpected::Error),
}
#[mockall::automock]
pub trait Store {
fn get(&self, domain: &domain::Name) -> Result<domain::Settings, GetError>;
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> unexpected::Result<()>;
fn all_domains(&self) -> unexpected::Result<Vec<domain::Name>>;
}
pub struct FSStore {
dir_path: path::PathBuf,
}
impl FSStore {
pub fn new(dir_path: &path::Path) -> io::Result<Self> {
fs::create_dir_all(dir_path)?;
Ok(Self {
dir_path: dir_path.into(),
})
}
fn settings_dir_path(&self, domain: &domain::Name) -> path::PathBuf {
self.dir_path.join(domain.as_str())
}
fn settings_file_path(&self, domain: &domain::Name) -> path::PathBuf {
self.settings_dir_path(domain).join("settings.json")
}
}
impl Store for FSStore {
fn get(&self, domain: &domain::Name) -> Result<domain::Settings, GetError> {
let path = self.settings_file_path(domain);
let settings_file = fs::File::open(path.as_path()).map_err(|e| match e.kind() {
io::ErrorKind::NotFound => GetError::NotFound,
_ => e
.into_unexpected_while(format!("opening {}", path.display()))
.into(),
})?;
let settings = serde_json::from_reader(settings_file)
.map_unexpected_while(|| format!("json parsing {}", path.display()))?;
Ok(settings)
}
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> unexpected::Result<()> {
let dir_path = self.settings_dir_path(domain);
fs::create_dir_all(dir_path.as_path())
.map_unexpected_while(|| format!("creating dir {}", dir_path.display()))?;
let file_path = self.settings_file_path(domain);
let settings_file = fs::File::create(file_path.as_path())
.map_unexpected_while(|| format!("creating file {}", file_path.display()))?;
serde_json::to_writer(settings_file, settings)
.map_unexpected_while(|| format!("writing config to {}", file_path.display()))?;
Ok(())
}
fn all_domains(&self) -> unexpected::Result<Vec<domain::Name>> {
fs::read_dir(&self.dir_path)
.or_unexpected()?
.map(
|dir_entry_res: io::Result<fs::DirEntry>| -> unexpected::Result<domain::Name> {
let domain = dir_entry_res.or_unexpected()?.file_name();
let domain = domain.to_str().ok_or(unexpected::Error::from(
"couldn't convert os string to &str",
))?;
domain
.parse()
.map_unexpected_while(|| format!("parsing {domain} as domain name"))
},
)
.try_collect()
}
}
#[cfg(test)]
mod tests {
use super::{Store, *};
use crate::domain;
use crate::origin::Descr;
use std::str::FromStr;
use tempdir::TempDir;
#[test]
fn basic() {
let tmp_dir = TempDir::new("domain_store").unwrap();
let store = FSStore::new(tmp_dir.path()).expect("store created");
let domain = domain::Name::from_str("foo.com").expect("domain parsed");
let settings = domain::Settings {
origin_descr: Descr::Git {
url: "http://bar".parse().unwrap(),
branch_name: "baz".to_string(),
},
add_path_prefix: None,
};
assert!(matches!(
store.get(&domain),
Err::<domain::Settings, GetError>(GetError::NotFound)
));
store.set(&domain, &settings).expect("set");
assert_eq!(settings, store.get(&domain).expect("settings retrieved"));
let new_settings = domain::Settings {
origin_descr: Descr::Git {
url: "https://BAR".parse().unwrap(),
branch_name: "BAZ".to_string(),
},
add_path_prefix: None,
};
store.set(&domain, &new_settings).expect("set");
assert_eq!(
new_settings,
store.get(&domain).expect("settings retrieved")
);
}
}