use std::{ collections::*, convert::{TryFrom, TryInto}, iter::FromIterator, path::Path, }; use serde::Deserialize; use thiserror::Error; #[derive (Error, Debug)] pub enum ConfigError { #[error ("I/O error")] Io (#[from] std::io::Error), #[error ("UTF-8 decoding failed")] Utf8 (#[from] std::string::FromUtf8Error), #[error ("TOML parsing failed")] Toml (#[from] toml::de::Error), #[error ("base64 decoding failed")] Base64Decode (#[from] base64::DecodeError), #[error ("tripcode not 32 bytes after decoding")] TripcodeBadLength, #[error ("unknown config error")] Unknown, } // Stuff we need to load from the config file and use to // set up the HTTP server pub mod file { use super::*; #[derive (Deserialize)] pub struct Server { pub tripcode: String, pub display_name: Option , } #[derive (Deserialize)] pub struct Config { pub port: Option , pub servers: HashMap , } } // Stuff we actually need at runtime pub struct Server { pub tripcode: blake3::Hash, pub display_name: Option , } pub struct Config { pub servers: HashMap , } impl TryFrom for Server { type Error = ConfigError; fn try_from (f: file::Server) -> Result { let bytes: Vec = base64::decode (f.tripcode)?; let bytes: [u8; 32] = (&bytes [..]).try_into ().map_err (|_| ConfigError::TripcodeBadLength)?; let tripcode = blake3::Hash::from (bytes); Ok (Self { tripcode, display_name: f.display_name, }) } } impl TryFrom for Config { type Error = ConfigError; fn try_from (f: file::Config) -> Result { let servers = f.servers.into_iter () .map (|(k, v)| Ok::<_, ConfigError> ((k, v.try_into ()?))); let servers = itertools::process_results (servers, |i| HashMap::from_iter (i))?; Ok (Self { servers, }) } } impl Config { pub async fn from_file (path: &Path) -> Result { use tokio::prelude::*; let mut f = tokio::fs::File::open (path).await?; let mut buffer = vec! [0u8; 4096]; let bytes_read = f.read (&mut buffer).await?; buffer.truncate (bytes_read); let config_s = String::from_utf8 (buffer)?; let new_config: file::Config = toml::from_str (&config_s)?; Self::try_from (new_config) } }