diff --git a/Cargo.lock b/Cargo.lock index 395a951..1dc55f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -392,6 +392,7 @@ dependencies = [ "mime_guess", "native-tls", "serde", + "thiserror", "toml", "url", ] @@ -419,6 +420,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinyvec" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index 320f493..fd5a919 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ toml = "0.5.7" anyhow = "1.0.34" clap = "2.33.3" mime_guess = "2.0.3" +thiserror = "1.0.22" diff --git a/src/error.rs b/src/error.rs index 1457493..a00e9b9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,26 @@ -pub struct SimpleError(String); +use std::io; +use thiserror::Error; -impl std::convert::From for SimpleError { - fn from(string: String) -> Self { - Self(string) - } -} +#[derive(Error, Debug)] +pub enum TaurusError { + #[error("failed to read configuration file: {0:#?}")] + InvalidConfig(anyhow::Error), + + #[error("failed to open identity file: {0}")] + NoIdentity(io::Error), + + #[error("failed parse certificate: {0:#?}")] + InvalidCertificate(native_tls::Error), + + #[error("failed to bind: {0}")] + BindFailed(io::Error), + + #[error("invalid Unicode input")] + InvalidUnicode, + + #[error("could not read the stream")] + StreamReadFailed(io::Error), -impl std::fmt::Debug for SimpleError { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - formatter.write_str(&self.0) - } + #[error("could not write to the stream")] + StreamWriteFailed(io::Error), } diff --git a/src/gemini.rs b/src/gemini.rs index d765021..b3860cb 100644 --- a/src/gemini.rs +++ b/src/gemini.rs @@ -1,3 +1,4 @@ +use crate::error; use native_tls::TlsStream; use std::io::Write; use std::net::TcpStream; @@ -54,7 +55,7 @@ impl GeminiResponse { } } - pub fn send(&self, mut stream: TlsStream) -> Result { + pub fn send(&self, mut stream: TlsStream) -> Result { let mut buf: Vec = Vec::new(); // @@ -74,6 +75,6 @@ impl GeminiResponse { stream .write(&buf) - .map_err(|e| format!("could not write to stream: {}", e)) + .map_err(error::TaurusError::StreamWriteFailed) } } diff --git a/src/main.rs b/src/main.rs index a58a23f..cf62819 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,14 @@ use std::thread; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; -fn main() -> Result<(), error::SimpleError> { +fn main() { + if let Err(e) = run() { + println!("Error: {}", e); + std::process::exit(1); + } +} + +fn run() -> Result<(), error::TaurusError> { // CLI let matches = App::new(crate_name!()) .version(crate_version!()) @@ -34,8 +41,8 @@ fn main() -> Result<(), error::SimpleError> { .get_matches(); let config_path = matches.value_of("config").map(|v| v.to_owned()); - let config: config::Config = config::Config::load(config_path) - .map_err(|err| format!("failed to read configuration file: {}", err))?; + let config: config::Config = + config::Config::load(config_path).map_err(error::TaurusError::InvalidConfig)?; // Defaults for configuration file let port = config.port.unwrap_or(1965); @@ -47,18 +54,13 @@ fn main() -> Result<(), error::SimpleError> { .unwrap_or_else(|| "/var/www/gemini".to_owned()); // Read certificate - let mut file = - File::open(cert_file).map_err(|err| format!("failed to open identity file: {}", err))?; - - let mut identity = vec![]; - file.read_to_end(&mut identity) - .map_err(|err| format!("failed to read identity file: {}", err))?; + let identity = read_file(&cert_file).map_err(error::TaurusError::NoIdentity)?; let identity = Identity::from_pkcs12(&identity, &config.certificate_password) - .map_err(|err| format!("failed to parse certificate: {}", err))?; + .map_err(error::TaurusError::InvalidCertificate)?; let address = format!("0.0.0.0:{}", port); - let listener = TcpListener::bind(address).map_err(|err| format!("failed to bind: {}", err))?; + let listener = TcpListener::bind(address).map_err(|err| error::TaurusError::BindFailed(err))?; let acceptor = TlsAcceptor::new(identity).unwrap(); let acceptor = Arc::new(acceptor); @@ -99,12 +101,12 @@ fn read_file(file_path: &str) -> Result, io::Error> { } /// Send file as a response -fn write_file(path: &str) -> Result { +fn write_file(path: &str) -> Result { let extension = path::Path::new(path) .extension() .unwrap_or_else(|| std::ffi::OsStr::new("")) .to_str() - .ok_or_else(|| "invalid Unicode".to_owned())?; + .ok_or(error::TaurusError::InvalidUnicode)?; let mime_type = match extension { "gmi" => "text/gemini; charset=utf-8", @@ -125,11 +127,15 @@ fn write_file(path: &str) -> Result { } } -fn handle_client(mut stream: TlsStream, static_root: &str) -> Result { +fn handle_client( + mut stream: TlsStream, + static_root: &str, +) -> Result { let mut buffer = [0; 1024]; - if let Err(e) = stream.read(&mut buffer) { - return Err(format!("could not read from stream: {}", e)); - } + + stream + .read(&mut buffer) + .map_err(error::TaurusError::StreamReadFailed)?; let mut raw_request = String::from_utf8_lossy(&buffer[..]).to_mut().to_owned(); @@ -158,12 +164,12 @@ fn handle_client(mut stream: TlsStream, static_root: &str) -> Result< let index_path = path .join("index.gmi") .to_str() - .ok_or_else(|| "invalid Unicode".to_owned())? + .ok_or(error::TaurusError::InvalidUnicode)? .to_owned(); write_file(&index_path)?.send(stream) } else { - write_file(path.to_str().ok_or_else(|| "invalid Unicode".to_owned())?)?.send(stream) + write_file(path.to_str().ok_or(error::TaurusError::InvalidUnicode)?)?.send(stream) } } else { GeminiResponse::not_found().send(stream)