use crate::magick::ValidInputType; use crate::serde_str::Serde; use clap::ArgEnum; use std::{fmt::Display, path::PathBuf, str::FromStr}; use tracing::Level; #[derive( Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize, ArgEnum, )] #[serde(rename_all = "snake_case")] pub(crate) enum LogFormat { Compact, Json, Normal, Pretty, } #[derive( Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize, ArgEnum, )] #[serde(rename_all = "snake_case")] pub(crate) enum ImageFormat { Jpeg, Webp, Png, } #[derive(Clone, Debug)] pub(crate) struct Targets { pub(crate) targets: tracing_subscriber::filter::Targets, } /// Configuration for filesystem media storage #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, clap::Parser)] #[serde(rename_all = "snake_case")] pub(crate) struct Filesystem { /// Path to store media #[clap(short, long)] pub(crate) path: PathBuf, } /// Configuration for object media storage #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, clap::Parser)] #[serde(rename_all = "snake_case")] pub(crate) struct ObjectStorage { /// The bucket in which to store media #[clap(short, long)] pub(crate) bucket_name: String, /// The region the bucket is located in #[clap(short, long)] pub(crate) region: Serde, /// The Access Key for the user accessing the bucket #[clap(short, long)] pub(crate) access_key: String, /// The secret key for the user accessing the bucket #[clap(short, long)] pub(crate) secret_key: String, /// The security token for accessing the bucket #[clap(long)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) security_token: Option, /// The session token for accessing the bucket #[clap(long)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) session_token: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] #[serde(tag = "type")] pub(crate) enum Store { Filesystem(Filesystem), ObjectStorage(ObjectStorage), } impl ImageFormat { pub(crate) fn as_hint(self) -> Option { Some(ValidInputType::from_format(self)) } pub(crate) fn as_magick_format(self) -> &'static str { match self { Self::Jpeg => "JPEG", Self::Png => "PNG", Self::Webp => "WEBP", } } } impl From for Store { fn from(f: Filesystem) -> Self { Self::Filesystem(f) } } impl From for Store { fn from(o: ObjectStorage) -> Self { Self::ObjectStorage(o) } } impl FromStr for Targets { type Err = ::Err; fn from_str(s: &str) -> Result { Ok(Targets { targets: s.parse()?, }) } } impl Display for Targets { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let targets = self .targets .iter() .map(|(path, level)| format!("{}={}", path, level)) .collect::>() .join(","); let max_level = [ Level::TRACE, Level::DEBUG, Level::INFO, Level::WARN, Level::ERROR, ] .iter() .fold(None, |found, level| { if found.is_none() && self .targets .would_enable("not_a_real_target_so_nothing_can_conflict", level) { Some(level.to_string().to_lowercase()) } else { found } }); if let Some(level) = max_level { if !targets.is_empty() { write!(f, "{},{}", level, targets) } else { write!(f, "{}", level) } } else if !targets.is_empty() { write!(f, "{}", targets) } else { Ok(()) } } } impl FromStr for ImageFormat { type Err = String; fn from_str(s: &str) -> Result { for variant in Self::value_variants() { if variant.to_possible_value().unwrap().matches(s, false) { return Ok(*variant); } } Err(format!("Invalid variant: {}", s)) } } impl FromStr for LogFormat { type Err = String; fn from_str(s: &str) -> Result { for variant in Self::value_variants() { if variant.to_possible_value().unwrap().matches(s, false) { return Ok(*variant); } } Err(format!("Invalid variant: {}", s)) } } impl Display for ImageFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.to_possible_value() .expect("no values are skipped") .get_name() .fmt(f) } } impl Display for LogFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.to_possible_value() .expect("no values are skipped") .get_name() .fmt(f) } } #[cfg(test)] mod tests { use super::{Serde, Targets}; #[test] fn builds_info_targets() { let t: Serde = "info".parse().unwrap(); println!("{:?}", t); assert_eq!(t.to_string(), "info"); } #[test] fn builds_specific_targets() { let t: Serde = "pict_rs=info".parse().unwrap(); assert_eq!(t.to_string(), "pict_rs=info"); } #[test] fn builds_warn_and_specific_targets() { let t: Serde = "warn,pict_rs=info".parse().unwrap(); assert_eq!(t.to_string(), "warn,pict_rs=info"); } }