diff --git a/src/domain.rs b/src/domain.rs index e7b4c92..74359fc 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -1,80 +1,29 @@ pub mod acme; pub mod checker; -pub mod config; pub mod manager; +mod name; +pub mod store; -use std::fmt; -use std::str::FromStr; +pub use name::*; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -use trust_dns_client::rr as trust_dns_rr; +use crate::error::unexpected::{self, Mappable}; +use crate::origin; -#[derive(Debug, Clone)] -/// Validated representation of a domain name -pub struct Name { - inner: trust_dns_rr::Name, - utf8_str: String, +use hex::ToHex; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Defines how a domain will behave when it is accessed. These are configured by the owner of the +/// domain during setup. +pub struct Domain { + pub origin_descr: origin::Descr, } -impl Name { - pub fn as_str(&self) -> &str { - self.utf8_str.as_str() - } -} - -impl fmt::Display for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.utf8_str) - } -} - -impl FromStr for Name { - type Err = ::Err; - - fn from_str(s: &str) -> Result { - let mut n = trust_dns_rr::Name::from_str(s)?; - let utf8_str = n.clone().to_utf8(); - - n.set_fqdn(true); - - Ok(Name { inner: n, utf8_str }) - } -} - -impl Serialize for Name { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.as_str()) - } -} - -struct NameVisitor; - -impl<'de> de::Visitor<'de> for NameVisitor { - type Value = Name; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "a valid domain name") - } - - fn visit_str(self, s: &str) -> Result - where - E: de::Error, - { - match Name::from_str(s) { - Ok(n) => Ok(n), - Err(e) => Err(E::custom(format!("invalid domain name: {}", e))), - } - } -} - -impl<'de> Deserialize<'de> for Name { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(NameVisitor) +impl Domain { + pub fn hash(&self) -> Result { + let mut h = Sha256::new(); + serde_json::to_writer(&mut h, self).or_unexpected()?; + Ok(h.finalize().encode_hex::()) } } diff --git a/src/domain/checker.rs b/src/domain/checker.rs index bc33a7f..216a683 100644 --- a/src/domain/checker.rs +++ b/src/domain/checker.rs @@ -62,7 +62,7 @@ impl DNSChecker { domain: &domain::Name, challenge_token: &str, ) -> Result<(), CheckDomainError> { - let domain = &domain.inner; + let domain = domain.as_rr(); // check that the A is installed correctly on the domain { diff --git a/src/domain/manager.rs b/src/domain/manager.rs index e228c30..ea443a9 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -1,4 +1,4 @@ -use crate::domain::{self, acme, checker, config}; +use crate::domain::{self, acme, checker, store}; use crate::error::unexpected::{self, Mappable}; use crate::origin; use crate::util; @@ -15,11 +15,11 @@ pub enum GetConfigError { Unexpected(#[from] unexpected::Error), } -impl From for GetConfigError { - fn from(e: config::GetError) -> GetConfigError { +impl From for GetConfigError { + fn from(e: store::GetError) -> GetConfigError { match e { - config::GetError::NotFound => GetConfigError::NotFound, - config::GetError::Unexpected(e) => GetConfigError::Unexpected(e), + store::GetError::NotFound => GetConfigError::NotFound, + store::GetError::Unexpected(e) => GetConfigError::Unexpected(e), } } } @@ -36,11 +36,11 @@ pub enum GetFileError { Unexpected(#[from] unexpected::Error), } -impl From for GetFileError { - fn from(e: config::GetError) -> Self { +impl From for GetFileError { + fn from(e: store::GetError) -> Self { match e { - config::GetError::NotFound => Self::DomainNotFound, - config::GetError::Unexpected(e) => Self::Unexpected(e), + store::GetError::NotFound => Self::DomainNotFound, + store::GetError::Unexpected(e) => Self::Unexpected(e), } } } @@ -69,11 +69,11 @@ pub enum SyncError { Unexpected(#[from] unexpected::Error), } -impl From for SyncError { - fn from(e: config::GetError) -> SyncError { +impl From for SyncError { + fn from(e: store::GetError) -> SyncError { match e { - config::GetError::NotFound => SyncError::NotFound, - config::GetError::Unexpected(e) => SyncError::Unexpected(e), + store::GetError::NotFound => SyncError::NotFound, + store::GetError::Unexpected(e) => SyncError::Unexpected(e), } } } @@ -122,10 +122,10 @@ impl From for SyncWithConfigError { } } -impl From for SyncWithConfigError { - fn from(e: config::SetError) -> SyncWithConfigError { +impl From for SyncWithConfigError { + fn from(e: store::SetError) -> SyncWithConfigError { match e { - config::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e), + store::SetError::Unexpected(e) => SyncWithConfigError::Unexpected(e), } } } @@ -134,7 +134,7 @@ pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyEr //#[mockall::automock] pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert { - fn get_config(&self, domain: &domain::Name) -> Result; + fn get_config(&self, domain: &domain::Name) -> Result; fn get_file<'store>( &'store self, @@ -150,7 +150,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert { fn sync_with_config<'mgr>( &'mgr self, domain: domain::Name, - config: config::Config, + config: domain::Domain, ) -> util::BoxFuture<'mgr, Result<(), SyncWithConfigError>>; fn get_acme_http01_challenge_key( @@ -163,7 +163,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert { pub struct ManagerImpl { origin_store: Box, - domain_config_store: Box, + domain_config_store: Box, domain_checker: checker::DNSChecker, acme_manager: Option>, } @@ -171,7 +171,7 @@ pub struct ManagerImpl { impl ManagerImpl { pub fn new< OriginStore: origin::Store + Send + Sync + 'static, - DomainConfigStore: config::Store + Send + Sync + 'static, + DomainConfigStore: store::Store + Send + Sync + 'static, AcmeManager: acme::manager::Manager + Send + Sync + 'static, >( task_stack: &mut util::TaskStack, @@ -224,7 +224,7 @@ impl ManagerImpl { } impl Manager for ManagerImpl { - fn get_config(&self, domain: &domain::Name) -> Result { + fn get_config(&self, domain: &domain::Name) -> Result { Ok(self.domain_config_store.get(domain)?) } @@ -254,7 +254,7 @@ impl Manager for ManagerImpl { fn sync_with_config<'mgr>( &'mgr self, domain: domain::Name, - config: config::Config, + config: domain::Domain, ) -> util::BoxFuture<'mgr, Result<(), SyncWithConfigError>> { Box::pin(async move { let config_hash = config diff --git a/src/domain/name.rs b/src/domain/name.rs new file mode 100644 index 0000000..ee462ca --- /dev/null +++ b/src/domain/name.rs @@ -0,0 +1,79 @@ +use std::fmt; +use std::str::FromStr; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use trust_dns_client::rr as trust_dns_rr; + +#[derive(Debug, Clone)] +/// Validated representation of a domain name +pub struct Name { + inner: trust_dns_rr::Name, + utf8_str: String, +} + +impl Name { + pub fn as_str(&self) -> &str { + self.utf8_str.as_str() + } + + pub fn as_rr(&self) -> &trust_dns_rr::Name { + &self.inner + } +} + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.utf8_str) + } +} + +impl FromStr for Name { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + let mut n = trust_dns_rr::Name::from_str(s)?; + let utf8_str = n.clone().to_utf8(); + + n.set_fqdn(true); + + Ok(Name { inner: n, utf8_str }) + } +} + +impl Serialize for Name { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +struct NameVisitor; + +impl<'de> de::Visitor<'de> for NameVisitor { + type Value = Name; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a valid domain name") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + match Name::from_str(s) { + Ok(n) => Ok(n), + Err(e) => Err(E::custom(format!("invalid domain name: {}", e))), + } + } +} + +impl<'de> Deserialize<'de> for Name { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(NameVisitor) + } +} diff --git a/src/domain/config.rs b/src/domain/store.rs similarity index 80% rename from src/domain/config.rs rename to src/domain/store.rs index 3022c61..4072b5f 100644 --- a/src/domain/config.rs +++ b/src/domain/store.rs @@ -2,26 +2,8 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fs, io}; +use crate::domain; use crate::error::unexpected::{self, Intoable, Mappable}; -use crate::{domain, origin}; - -use hex::ToHex; -use serde::{Deserialize, Serialize}; -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: origin::Descr, -} - -impl Config { - pub fn hash(&self) -> Result { - let mut h = Sha256::new(); - serde_json::to_writer(&mut h, self).or_unexpected()?; - Ok(h.finalize().encode_hex::()) - } -} #[derive(thiserror::Error, Debug)] pub enum GetError { @@ -40,8 +22,8 @@ pub enum SetError { #[mockall::automock] pub trait Store { - fn get(&self, domain: &domain::Name) -> Result; - fn set(&self, domain: &domain::Name, config: &Config) -> Result<(), SetError>; + fn get(&self, domain: &domain::Name) -> Result; + fn set(&self, domain: &domain::Name, config: &domain::Domain) -> Result<(), SetError>; fn all_domains(&self) -> Result, unexpected::Error>; } @@ -67,7 +49,7 @@ impl FSStore { } impl Store for FSStore { - fn get(&self, domain: &domain::Name) -> Result { + fn get(&self, domain: &domain::Name) -> Result { let path = self.config_file_path(domain); let config_file = fs::File::open(path.as_path()).map_err(|e| match e.kind() { io::ErrorKind::NotFound => GetError::NotFound, @@ -81,7 +63,7 @@ impl Store for FSStore { Ok(config) } - fn set(&self, domain: &domain::Name, config: &Config) -> Result<(), SetError> { + fn set(&self, domain: &domain::Name, config: &domain::Domain) -> Result<(), SetError> { let dir_path = self.config_dir_path(domain); fs::create_dir_all(dir_path.as_path()) .map_unexpected_while(|| format!("creating dir {}", dir_path.display()))?; @@ -132,7 +114,7 @@ mod tests { let domain = domain::Name::from_str("foo.com").expect("domain parsed"); - let config = Config { + let config = domain::Domain { origin_descr: Descr::Git { url: "bar".to_string(), branch_name: "baz".to_string(), @@ -141,13 +123,13 @@ mod tests { assert!(matches!( store.get(&domain), - Err::(GetError::NotFound) + Err::(GetError::NotFound) )); store.set(&domain, &config).expect("config set"); assert_eq!(config, store.get(&domain).expect("config retrieved")); - let new_config = Config { + let new_config = domain::Domain { origin_descr: Descr::Git { url: "BAR".to_string(), branch_name: "BAZ".to_string(), diff --git a/src/main.rs b/src/main.rs index 6a40329..a227714 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,7 +88,7 @@ async fn main() { .expect("domain checker initialization failed"); let domain_config_store = - domani::domain::config::FSStore::new(&config.domain_config_store_dir_path) + domani::domain::store::FSStore::new(&config.domain_config_store_dir_path) .expect("domain config store initialization failed"); let domain_acme_manager = if config.https_listen_addr.is_some() { diff --git a/src/service/http.rs b/src/service/http.rs index bcf7b82..c3836e2 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -192,7 +192,7 @@ impl<'svc> Service { #[derive(Serialize)] struct Response { domain: domain::Name, - config: Option, + config: Option, } let config = match self.domain_manager.get_config(&args.domain) { @@ -231,7 +231,7 @@ impl<'svc> Service { challenge_token: String, } - let config: domain::config::Config = match domain_config.try_into() { + let config: domain::Domain = match domain_config.try_into() { Ok(Some(config)) => config, Ok(None) => return self.render_error_page(400, "domain config is required"), Err(e) => { @@ -268,7 +268,7 @@ impl<'svc> Service { return self.render_error_page(401, "Incorrect passphrase"); } - let config: domain::config::Config = match domain_config.try_into() { + let config: domain::Domain = match domain_config.try_into() { Ok(Some(config)) => config, Ok(None) => return self.render_error_page(400, "domain config is required"), Err(e) => { diff --git a/src/service/util.rs b/src/service/util.rs index ebb9b90..f6a9e75 100644 --- a/src/service/util.rs +++ b/src/service/util.rs @@ -11,7 +11,7 @@ pub struct FlatConfig { config_origin_descr_git_branch_name: Option, } -impl TryFrom for Option { +impl TryFrom for Option { type Error = String; fn try_from(v: FlatConfig) -> Result { @@ -21,7 +21,7 @@ impl TryFrom for Option { .as_str() { "" => Ok(None), - "git" => Ok(Some(domain::config::Config { + "git" => Ok(Some(domain::Domain { origin_descr: origin::Descr::Git { url: v .config_origin_descr_git_url @@ -36,8 +36,8 @@ impl TryFrom for Option { } } -impl From for FlatConfig { - fn from(v: domain::config::Config) -> Self { +impl From for FlatConfig { + fn from(v: domain::Domain) -> Self { match v.origin_descr { origin::Descr::Git { url, branch_name } => FlatConfig { config_origin_descr_kind: Some("git".to_string()),