use std::ops::Deref;
use serde::{de::Error as _, Deserialize, Deserializer, Serialize};
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
strum::AsRefStr,
strum::Display,
strum::EnumCount,
strum::EnumIter,
strum::EnumString,
strum::VariantNames,
strum::IntoStaticStr,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[serde(tag = "format")]
pub enum OutputFormat {
Pdf {
#[serde(default, skip_serializing_if = "crate::utils::is_default")]
engine: PdfEngine,
#[serde(default, skip_serializing_if = "crate::utils::is_default")]
theme: Option<Theme>,
},
Docx {
#[serde(default, skip_serializing_if = "crate::utils::is_default")]
theme: Option<Theme>,
},
Odt {
#[serde(default, skip_serializing_if = "crate::utils::is_default")]
theme: Option<Theme>,
},
Markdown {
#[serde(default, skip_serializing_if = "crate::utils::is_default")]
theme: Option<Theme>,
},
Commonmark {
#[serde(default, skip_serializing_if = "crate::utils::is_default")]
theme: Option<Theme>,
},
CommonmarkX {
#[serde(default, skip_serializing_if = "crate::utils::is_default")]
theme: Option<Theme>,
},
}
impl OutputFormat {
pub fn default_pdf() -> Self {
Self::Pdf {
engine: Default::default(),
theme: Default::default(),
}
}
}
impl Default for OutputFormat {
fn default() -> Self {
Self::default_pdf()
}
}
impl OutputFormat {
pub fn theme(&self) -> Option<&str> {
match self {
Self::Pdf { theme, .. }
| Self::Docx { theme }
| Self::Odt { theme }
| Self::Markdown { theme }
| Self::Commonmark { theme }
| Self::CommonmarkX { theme } => theme.as_deref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Theme(String);
impl Theme {
pub fn new(theme: String) -> Result<Self, &'static str> {
if !theme
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err("Only alphanumeric chars and `-_` are allowed");
}
Ok(Self(theme))
}
pub fn inner(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl<'de> Deserialize<'de> for Theme {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let inner = String::deserialize(deserializer)?;
Self::new(inner).map_err(D::Error::custom)
}
}
impl Deref for Theme {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_str()
}
}
impl TryFrom<String> for Theme {
type Error = &'static str;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum PdfEngine {
#[default]
Latex,
LibreOffice,
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use strum::VariantNames;
use super::*;
#[test]
fn default_value() {
assert_eq!(
OutputFormat::Pdf {
engine: Default::default(),
theme: Default::default(),
},
OutputFormat::default()
);
}
#[test]
fn serialize_strum_variant_names() {
assert_eq!(
"pdf",
OutputFormat::Pdf {
engine: Default::default(),
theme: Default::default(),
}
.as_ref()
);
assert_eq!(
"docx",
OutputFormat::Docx {
theme: Default::default(),
}
.as_ref()
);
assert_eq!(
"odt",
OutputFormat::Odt {
theme: Default::default(),
}
.as_ref()
);
assert_eq!(
"markdown",
OutputFormat::Markdown {
theme: Default::default(),
}
.as_ref()
);
assert_eq!(
"commonmark",
OutputFormat::Commonmark {
theme: Default::default(),
}
.as_ref()
);
assert_eq!(
"commonmark_x",
OutputFormat::CommonmarkX {
theme: Default::default(),
}
.as_ref()
);
}
#[test]
fn iterate_strum_variant_names() {
assert_eq!(
[
"pdf",
"docx",
"odt",
"markdown",
"commonmark",
"commonmark_x",
],
OutputFormat::VARIANTS
);
}
#[test]
fn serialize_json() {
assert_eq!(
json!({
"format": "pdf",
}),
serde_json::to_value(OutputFormat::Pdf {
engine: Default::default(),
theme: Default::default(),
})
.unwrap()
);
assert_eq!(
json!({
"format": "docx",
}),
serde_json::to_value(OutputFormat::Docx {
theme: Default::default(),
})
.unwrap()
);
assert_eq!(
json!({
"format": "odt",
}),
serde_json::to_value(OutputFormat::Odt {
theme: Default::default(),
})
.unwrap()
);
assert_eq!(
json!({
"format": "markdown",
}),
serde_json::to_value(OutputFormat::Markdown {
theme: Default::default(),
})
.unwrap()
);
assert_eq!(
json!({
"format": "commonmark",
}),
serde_json::to_value(OutputFormat::Commonmark {
theme: Default::default(),
})
.unwrap()
);
assert_eq!(
json!({
"format": "commonmark_x",
}),
serde_json::to_value(OutputFormat::CommonmarkX {
theme: Default::default(),
})
.unwrap()
);
}
#[test]
fn deserialize_json() {
assert_eq!(
OutputFormat::Pdf {
engine: Default::default(),
theme: Default::default(),
},
serde_json::from_value(json!({"format": "pdf"})).unwrap()
);
assert_eq!(
OutputFormat::Docx {
theme: Default::default(),
},
serde_json::from_value(json!({"format": "docx"})).unwrap()
);
assert_eq!(
OutputFormat::Odt {
theme: Default::default(),
},
serde_json::from_value(json!({"format": "odt"})).unwrap()
);
assert_eq!(
OutputFormat::Markdown {
theme: Default::default(),
},
serde_json::from_value(json!({"format": "markdown"})).unwrap()
);
assert_eq!(
OutputFormat::Commonmark {
theme: Default::default(),
},
serde_json::from_value(json!({"format": "commonmark"})).unwrap()
);
assert_eq!(
OutputFormat::CommonmarkX {
theme: Default::default(),
},
serde_json::from_value(json!({"format": "commonmark_x"})).unwrap()
);
assert!(serde_json::from_value::<OutputFormat>(
json!({"format": "commonmark_x", "theme": "/asd/"})
)
.is_err());
}
#[test]
fn theme_invalid() {
assert!(Theme::try_from("value".to_string()).is_ok());
assert!(Theme::try_from("/".to_string()).is_err());
assert!(Theme::try_from("..".to_string()).is_err());
}
}