diff --git a/src/domain/checker.rs b/src/domain/checker.rs index 971adfd..6423eba 100644 --- a/src/domain/checker.rs +++ b/src/domain/checker.rs @@ -1,9 +1,9 @@ -use std::error::Error; use std::net; use std::str::FromStr; use std::sync; -use crate::domain; +use crate::error::MapUnexpected; +use crate::{domain, error}; use trust_dns_client::client::{AsyncClient, ClientHandle}; use trust_dns_client::rr::{DNSClass, Name, RData, RecordType}; @@ -15,7 +15,7 @@ pub enum NewDNSCheckerError { InvalidResolverAddress, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } #[derive(thiserror::Error, Debug)] @@ -27,7 +27,7 @@ pub enum CheckDomainError { ChallengeTokenNotSet, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } pub struct DNSChecker { @@ -50,7 +50,7 @@ pub fn new( let (client, bg) = tokio_runtime .block_on(async { AsyncClient::connect(stream).await }) - .map_err(|e| NewDNSCheckerError::Unexpected(Box::from(e)))?; + .map_unexpected()?; tokio_runtime.spawn(bg); @@ -68,18 +68,15 @@ impl DNSChecker { ) -> Result<(), CheckDomainError> { let domain = &domain.inner; - // check that the AAAA is installed correctly on the domain + // check that the A is installed correctly on the domain { - let response = match self + let response = self .client .lock() .await .query(domain.clone(), DNSClass::IN, RecordType::A) .await - { - Ok(res) => res, - Err(e) => return Err(CheckDomainError::Unexpected(Box::from(e))), - }; + .map_unexpected()?; let records = response.answers(); @@ -98,20 +95,17 @@ impl DNSChecker { // check that the TXT record with the challenge token is correctly installed on the domain { let domain = Name::from_str("_domiply_challenge") - .map_err(|e| CheckDomainError::Unexpected(Box::from(e)))? + .map_unexpected()? .append_domain(domain) - .map_err(|e| CheckDomainError::Unexpected(Box::from(e)))?; + .map_unexpected()?; - let response = match self + let response = self .client .lock() .await .query(domain, DNSClass::IN, RecordType::TXT) .await - { - Ok(res) => res, - Err(e) => return Err(CheckDomainError::Unexpected(Box::from(e))), - }; + .map_unexpected()?; let records = response.answers(); diff --git a/src/domain/config.rs b/src/domain/config.rs index 9b7cdcd..b7f231b 100644 --- a/src/domain/config.rs +++ b/src/domain/config.rs @@ -1,9 +1,8 @@ -use std::error::Error; use std::path::{Path, PathBuf}; use std::{fs, io, sync}; -use crate::domain; -use crate::origin::Descr; +use crate::error::{MapUnexpected, ToUnexpected}; +use crate::{domain, error, origin}; use hex::ToHex; use serde::{Deserialize, Serialize}; @@ -12,13 +11,13 @@ use sha2::{Digest, Sha256}; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] /// Values which the owner of a domain can configure when they install a domain. pub struct Config { - pub origin_descr: Descr, + pub origin_descr: origin::Descr, } impl Config { - pub fn hash(&self) -> Result> { + pub fn hash(&self) -> Result { let mut h = Sha256::new(); - serde_json::to_writer(&mut h, self)?; + serde_json::to_writer(&mut h, self).map_unexpected()?; Ok(h.finalize().encode_hex::()) } } @@ -29,13 +28,13 @@ pub enum GetError { NotFound, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } #[derive(thiserror::Error, Debug)] pub enum SetError { #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } #[mockall::automock] @@ -74,21 +73,19 @@ impl Store for sync::Arc { let config_file = fs::File::open(self.config_file_path(domain)).map_err(|e| match e.kind() { io::ErrorKind::NotFound => GetError::NotFound, - _ => GetError::Unexpected(Box::from(e)), + _ => e.to_unexpected().into(), })?; - serde_json::from_reader(config_file).map_err(|e| GetError::Unexpected(Box::from(e))) + let config = serde_json::from_reader(config_file).map_unexpected()?; + Ok(config) } fn set(&self, domain: &domain::Name, config: &Config) -> Result<(), SetError> { - fs::create_dir_all(self.config_dir_path(domain)) - .map_err(|e| SetError::Unexpected(Box::from(e)))?; + fs::create_dir_all(self.config_dir_path(domain)).map_unexpected()?; - let config_file = fs::File::create(self.config_file_path(domain)) - .map_err(|e| SetError::Unexpected(Box::from(e)))?; + let config_file = fs::File::create(self.config_file_path(domain)).map_unexpected()?; - serde_json::to_writer(config_file, config) - .map_err(|e| SetError::Unexpected(Box::from(e)))?; + serde_json::to_writer(config_file, config).map_unexpected()?; Ok(()) } diff --git a/src/domain/manager.rs b/src/domain/manager.rs index 9d22e37..8de0e48 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -1,7 +1,7 @@ use crate::domain::{self, checker, config}; -use crate::origin; +use crate::error::{MapUnexpected, ToUnexpected}; +use crate::{error, origin}; -use std::error::Error; use std::future; use std::{pin, sync}; @@ -11,7 +11,7 @@ pub enum GetConfigError { NotFound, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } impl From for GetConfigError { @@ -29,7 +29,7 @@ pub enum GetOriginError { NotFound, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } impl From for GetOriginError { @@ -50,7 +50,7 @@ pub enum SyncError { AlreadyInProgress, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } impl From for SyncError { @@ -80,7 +80,7 @@ pub enum SyncWithConfigError { ChallengeTokenNotSet, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } impl From for SyncWithConfigError { @@ -198,7 +198,7 @@ where .origin_store .get(config.origin_descr) // if there's a config there should be an origin, any error here is unexpected - .map_err(|e| GetOriginError::Unexpected(Box::from(e)))?; + .map_unexpected()?; Ok(origin) } @@ -208,7 +208,7 @@ where .sync(config.origin_descr, origin::store::Limits {}) .map_err(|e| match e { origin::store::SyncError::AlreadyInProgress => SyncError::AlreadyInProgress, - _ => SyncError::Unexpected(Box::from(e)), + _ => e.to_unexpected().into(), })?; Ok(()) } @@ -219,9 +219,7 @@ where config: config::Config, ) -> Self::SyncWithConfigFuture<'_> { Box::pin(async move { - let config_hash = config - .hash() - .map_err(|e| SyncWithConfigError::Unexpected(e))?; + let config_hash = config.hash().map_unexpected()?; self.domain_checker .check_domain(&domain, &config_hash) diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..09e8ccd --- /dev/null +++ b/src/error.rs @@ -0,0 +1,50 @@ +use std::error; +use std::fmt; +use std::fmt::Write; + +#[derive(Debug, Clone)] +pub struct Unexpected(String); + +impl fmt::Display for Unexpected { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Unexpected error occurred: {}", self.0) + } +} + +impl error::Error for Unexpected {} + +impl From<&str> for Unexpected { + fn from(s: &str) -> Self { + Unexpected(s.to_string()) + } +} + +pub trait ToUnexpected { + fn to_unexpected(&self) -> Unexpected; +} + +impl ToUnexpected for E { + fn to_unexpected(&self) -> Unexpected { + let mut w = String::new(); + write!(w, "{}", self.to_string()).expect("underlying error formatted correctly"); + + if let Some(src) = self.source() { + write!(w, " [{src}]").expect("underyling source formatted correctly"); + } + + Unexpected(w.to_string()) + } +} + +pub trait MapUnexpected { + fn map_unexpected(self) -> Result; +} + +impl MapUnexpected for Result { + fn map_unexpected(self) -> Result { + match self { + Ok(t) => Ok(t), + Err(err) => Err(err.to_unexpected()), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 174e28e..97d4b34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod domain; +pub mod error; pub mod origin; pub mod service; diff --git a/src/origin.rs b/src/origin.rs index 5245190..5a61708 100644 --- a/src/origin.rs +++ b/src/origin.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use crate::error; mod descr; pub use self::descr::Descr; @@ -10,7 +10,7 @@ pub enum ReadFileIntoError { FileNotFound, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } #[mockall::automock] diff --git a/src/origin/store.rs b/src/origin/store.rs index 1f8ca2e..8a5c3bb 100644 --- a/src/origin/store.rs +++ b/src/origin/store.rs @@ -1,5 +1,4 @@ -use crate::origin; -use std::error::Error; +use crate::{error, origin}; pub mod git; @@ -20,7 +19,7 @@ pub enum SyncError { AlreadyInProgress, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } #[derive(thiserror::Error, Debug)] @@ -29,13 +28,13 @@ pub enum GetError { NotFound, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } #[derive(thiserror::Error, Debug)] pub enum AllDescrsError { #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } /// Used in the return from all_descrs from Store. diff --git a/src/origin/store/git.rs b/src/origin/store/git.rs index 9d004b2..5fcabc8 100644 --- a/src/origin/store/git.rs +++ b/src/origin/store/git.rs @@ -1,7 +1,7 @@ -use crate::origin; +use crate::error::{MapUnexpected, ToUnexpected}; use crate::origin::store; +use crate::{error, origin}; -use std::error::Error; use std::path::{Path, PathBuf}; use std::{collections, fs, io, sync}; @@ -28,17 +28,16 @@ impl origin::Origin for sync::Arc { let file_object = repo .find_object(self.tree_object_id) - .map_err(|e| origin::ReadFileIntoError::Unexpected(Box::from(e)))? + .map_unexpected()? .peel_to_tree() - .map_err(|e| origin::ReadFileIntoError::Unexpected(Box::from(e)))? + .map_unexpected()? .lookup_entry_by_path(clean_path) - .map_err(|e| origin::ReadFileIntoError::Unexpected(Box::from(e)))? + .map_unexpected()? .ok_or(origin::ReadFileIntoError::FileNotFound)? .object() - .map_err(|e| origin::ReadFileIntoError::Unexpected(Box::from(e)))?; + .map_unexpected()?; - into.write_all(file_object.data.as_ref()) - .map_err(|e| origin::ReadFileIntoError::Unexpected(Box::from(e)))?; + into.write_all(file_object.data.as_ref()).map_unexpected()?; Ok(()) } @@ -50,7 +49,7 @@ enum GetOriginError { InvalidBranchName, #[error(transparent)] - Unexpected(Box), + Unexpected(#[from] error::Unexpected), } /// git::Store implements the Store trait for any Descr::Git based Origins. If any non-git @@ -98,17 +97,17 @@ impl Store { let commit_object_id = repo .try_find_reference(&self.branch_ref(branch_name)) - .map_err(|e| GetOriginError::Unexpected(Box::from(e)))? + .map_unexpected()? .ok_or(GetOriginError::InvalidBranchName)? .peel_to_id_in_place() - .map_err(|e| GetOriginError::Unexpected(Box::from(e)))? + .map_unexpected()? .detach(); let tree_object_id = repo .find_object(commit_object_id) - .map_err(|e| GetOriginError::Unexpected(Box::from(e)))? + .map_unexpected()? .try_to_commit_ref() - .map_err(|e| GetOriginError::Unexpected(Box::from(e)))? + .map_unexpected()? .tree(); Ok(sync::Arc::from(Origin { @@ -132,8 +131,7 @@ impl Store { // if the path doesn't exist then use the gix clone feature to clone it into the // directory. if fs::read_dir(repo_path).is_err() { - fs::create_dir_all(repo_path) - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))?; + fs::create_dir_all(repo_path).map_unexpected()?; let origin::Descr::Git { ref url, @@ -148,7 +146,7 @@ impl Store { gixCloneErr::UrlParse(_) | gixCloneErr::CanonicalizeUrl { .. } => { store::SyncError::InvalidURL } - _ => store::SyncError::Unexpected(Box::from(e)), + _ => e.to_unexpected().into(), })? .fetch_only(Discard, should_interrupt) .map_err(|_| store::SyncError::InvalidURL)?; @@ -156,36 +154,35 @@ impl Store { // Check to make sure the branch name exists // TODO if this fails we should delete repo_path repo.try_find_reference(&self.branch_ref(branch_name)) - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))? + .map_unexpected()? .ok_or(store::SyncError::InvalidBranchName)?; // Add the descr to the repo directory, so we can know the actual descr later // TODO if this fails we should delete repo_path - let descr_file = fs::File::create(self.descr_file_path(descr.id().as_ref())) - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))?; + let descr_file = + fs::File::create(self.descr_file_path(descr.id().as_ref())).map_unexpected()?; - serde_json::to_writer(descr_file, &descr) - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))?; + serde_json::to_writer(descr_file, &descr).map_unexpected()?; return Ok(repo); } let direction = gix::remote::Direction::Fetch; - let repo = gix::open(repo_path).map_err(|e| store::SyncError::Unexpected(Box::from(e)))?; + let repo = gix::open(repo_path).map_unexpected()?; let remote = repo .find_default_remote(direction) - .ok_or_else(|| store::SyncError::Unexpected(Box::from("no default configured")))? - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))?; + .ok_or_else(|| error::Unexpected::from("no default configured"))? + .map_unexpected()?; remote .connect(direction) - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))? + .map_unexpected()? .prepare_fetch(Discard, Default::default()) - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))? + .map_unexpected()? .receive(Discard, should_interrupt) - .map_err(|e| store::SyncError::Unexpected(Box::from(e)))?; + .map_unexpected()?; Ok(repo) } @@ -256,15 +253,15 @@ impl super::Store for sync::Arc { fs::read_dir(&repo_path).map_err(|e| match e.kind() { io::ErrorKind::NotFound => store::GetError::NotFound, - _ => store::GetError::Unexpected(Box::from(e)), + _ => e.to_unexpected().into(), })?; - let repo = gix::open(&repo_path).map_err(|e| store::GetError::Unexpected(Box::from(e)))?; + let repo = gix::open(&repo_path).map_unexpected()?; let origin = self.get_origin(repo, descr.clone()).map_err(|e| match e { // it's not expected that the branch name is invalid at this point, it must have // existed for sync to have been successful. - GetOriginError::InvalidBranchName => store::GetError::Unexpected(Box::from(e)), + GetOriginError::InvalidBranchName => e.to_unexpected().into(), GetOriginError::Unexpected(e) => store::GetError::Unexpected(e), })?; @@ -276,36 +273,30 @@ impl super::Store for sync::Arc { fn all_descrs(&self) -> store::AllDescrsResult> { Ok(Box::from( - fs::read_dir(&self.dir_path) - .map_err(|e| store::AllDescrsError::Unexpected(Box::from(e)))? - .map( - |dir_entry_res: io::Result| -> store::AllDescrsResult { - let descr_id: String = dir_entry_res - .map_err(|e| store::AllDescrsError::Unexpected(Box::from(e)))? - .file_name() - .to_str() - .ok_or_else(|| { - store::AllDescrsError::Unexpected(Box::from( - "couldn't convert os string to &str", - )) - })? - .into(); - - let descr_file_path = self.descr_file_path(descr_id.as_ref()); - - // TODO it's possible that opening the file will fail if syncing is - // still ongoing, as writing the descr file is the last step after - // initial sync has succeeded. - let descr_file = fs::File::open(descr_file_path) - .map_err(|e| store::AllDescrsError::Unexpected(Box::from(e)))?; - - let descr = serde_json::from_reader(descr_file) - .map_err(|e| store::AllDescrsError::Unexpected(Box::from(e)))?; - - Ok(descr) - }, - ), - )) + fs::read_dir(&self.dir_path).map_unexpected()?.map( + |dir_entry_res: io::Result| -> store::AllDescrsResult { + let descr_id: String = dir_entry_res + .map_unexpected()? + .file_name() + .to_str() + .ok_or_else(|| { + error::Unexpected::from("couldn't convert os string to &str") + })? + .into(); + + let descr_file_path = self.descr_file_path(descr_id.as_ref()); + + // TODO it's possible that opening the file will fail if syncing is + // still ongoing, as writing the descr file is the last step after + // initial sync has succeeded. + let descr_file = fs::File::open(descr_file_path).map_unexpected()?; + + let descr = serde_json::from_reader(descr_file).map_unexpected()?; + + Ok(descr) + }, + ), + )) } }