From 563f072e098197d0870c7d78543b121ff65747bd Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 7 May 2023 18:07:31 +0200 Subject: [PATCH] implement domain config store --- Cargo.toml | 2 +- src/domain.rs | 1 + src/domain/checker.rs | 3 +- src/domain/config.rs | 127 ++++++++++++++++++++++++++++++++++++++++++ src/origin/store.rs | 14 +++-- 5 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 src/domain/config.rs diff --git a/Cargo.toml b/Cargo.toml index 41646fe..4a1d6cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dev-dependencies] clippy = "0.0.302" +tempdir = "0.3.7" [dependencies] sha2 = "0.10.6" @@ -15,7 +16,6 @@ gix = { version = "0.44.1", features = [ "blocking-network-client", "blocking-http-transport-reqwest-rust-tls", ]} -tempdir = "0.3.7" serde = { version = "1.0.162", features = [ "derive" ]} serde_json = "1.0.96" trust-dns-client = "0.22.0" diff --git a/src/domain.rs b/src/domain.rs index ea64c55..fd51183 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -1 +1,2 @@ pub mod checker; +pub mod config; diff --git a/src/domain/checker.rs b/src/domain/checker.rs index a885e8f..e581b90 100644 --- a/src/domain/checker.rs +++ b/src/domain/checker.rs @@ -1,12 +1,11 @@ use std::error::Error; use std::str::FromStr; +use mockall::automock; use trust_dns_client::client::{Client, SyncClient}; use trust_dns_client::rr::{DNSClass, Name, RData, RecordType}; use trust_dns_client::udp::UdpClientConnection; -use mockall::automock; - #[derive(Debug)] pub enum NewDNSCheckerError { InvalidResolverAddress, diff --git a/src/domain/config.rs b/src/domain/config.rs new file mode 100644 index 0000000..d954495 --- /dev/null +++ b/src/domain/config.rs @@ -0,0 +1,127 @@ +use std::error::Error; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +use crate::origin::Descr; + +use mockall::automock; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +/// Values which the owner of a domain can configure when they install a domain. +pub struct Config { + pub origin_descr: Descr, +} + +#[derive(Debug)] +pub enum GetError { + NotFound, + Unexpected(Box), +} + +impl From for GetError { + fn from(e: E) -> GetError { + GetError::Unexpected(Box::from(e)) + } +} + +#[derive(Debug)] +pub enum SetError { + NotFound, + Unexpected(Box), +} + +impl From for SetError { + fn from(e: E) -> SetError { + SetError::Unexpected(Box::from(e)) + } +} + +#[automock] +pub trait Store { + fn get(&self, domain: &str) -> Result; + fn set(&self, domain: &str, config: &Config) -> Result<(), SetError>; +} + +pub struct FSStore { + dir_path: PathBuf, +} + +impl FSStore { + pub fn new(dir_path: &Path) -> io::Result { + fs::create_dir_all(dir_path)?; + Ok(FSStore { + dir_path: dir_path.into(), + }) + } + + fn config_dir_path(&self, domain: &str) -> PathBuf { + self.dir_path.join(domain) + } + + fn config_file_path(&self, domain: &str) -> PathBuf { + self.config_dir_path(domain).join("config.json") + } +} + +impl Store for FSStore { + fn get(&self, domain: &str) -> Result { + let config_file = + fs::File::open(self.config_file_path(domain)).map_err(|e| match e.kind() { + io::ErrorKind::NotFound => GetError::NotFound, + _ => e.into(), + })?; + + Ok(serde_json::from_reader(config_file)?) + } + + fn set(&self, domain: &str, config: &Config) -> Result<(), SetError> { + fs::create_dir_all(self.config_dir_path(domain))?; + + let config_file = fs::File::create(self.config_file_path(domain))?; + serde_json::to_writer(config_file, config)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::origin::Descr; + use tempdir::TempDir; + + #[test] + fn basic() { + let tmp_dir = TempDir::new("domain_config_store").unwrap(); + + let store = FSStore::new(tmp_dir.path()).expect("store created"); + + let domain = "foo"; + + let config = Config { + origin_descr: Descr::Git { + url: "bar".to_string(), + branch_name: "baz".to_string(), + }, + }; + + assert!(matches!( + store.get(domain), + Err::(GetError::NotFound) + )); + + store.set(domain, &config).expect("config set"); + assert_eq!(config, store.get(domain).expect("config retrieved")); + + let new_config = Config { + origin_descr: Descr::Git { + url: "BAR".to_string(), + branch_name: "BAZ".to_string(), + }, + }; + + store.set(domain, &new_config).expect("config set"); + assert_eq!(new_config, store.get(domain).expect("config retrieved")); + } +} diff --git a/src/origin/store.rs b/src/origin/store.rs index 5cff29a..a141daf 100644 --- a/src/origin/store.rs +++ b/src/origin/store.rs @@ -1,5 +1,4 @@ use super::Descr; -use serde_json; use std::error::Error; #[derive(Clone, Copy)] @@ -44,7 +43,12 @@ impl From for AllDescrsError { type AllDescrsResult = Result; pub trait Store { - fn write_file(&self, descr: &Descr, path: &str, into: &mut W) -> Result<(), WriteFileError> + fn read_file_into( + &self, + descr: &Descr, + path: &str, + into: &mut W, + ) -> Result<(), WriteFileError> where W: std::io::Write + ?Sized; @@ -140,7 +144,7 @@ pub mod git { Ok(()) } - fn write_file( + fn read_file_into( &self, descr: &Descr, path: &str, @@ -235,7 +239,7 @@ pub mod git { let assert_write = |path: &str| { let mut into: Vec = vec![]; store - .write_file(&descr, path, &mut into) + .read_file_into(&descr, path, &mut into) .expect("write should succeed"); assert!(into.len() > 0); }; @@ -246,7 +250,7 @@ pub mod git { // File doesn't exist let mut into: Vec = vec![]; assert!(matches!( - store.write_file(&descr, "DNE", &mut into), + store.read_file_into(&descr, "DNE", &mut into), Err::<(), super::WriteFileError>(super::WriteFileError::FileNotFound), )); assert_eq!(into.len(), 0);