use serde::Serialize;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct TextStyle {
#[serde(skip_serializing_if = "Option::is_none")]
pub bold: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub italic: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub underline: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strikethrough: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, clap::ValueEnum)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Alignment {
Start,
Center,
End,
Justified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, clap::ValueEnum)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NamedStyle {
NormalText,
Title,
Subtitle,
#[serde(rename = "HEADING_1")]
Heading1,
#[serde(rename = "HEADING_2")]
Heading2,
#[serde(rename = "HEADING_3")]
Heading3,
#[serde(rename = "HEADING_4")]
Heading4,
#[serde(rename = "HEADING_5")]
Heading5,
#[serde(rename = "HEADING_6")]
Heading6,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct ParagraphStyle {
#[serde(skip_serializing_if = "Option::is_none")]
pub alignment: Option<Alignment>,
#[serde(rename = "namedStyleType", skip_serializing_if = "Option::is_none")]
pub named_style_type: Option<NamedStyle>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum StylePatch {
#[serde(rename = "textStyle")]
Text(TextStyle),
#[serde(rename = "paragraphStyle")]
Paragraph(ParagraphStyle),
}
impl StylePatch {
#[must_use]
pub fn fields(&self) -> String {
let fields: Vec<&str> = match self {
Self::Text(style) => [
(style.bold.is_some(), "bold"),
(style.italic.is_some(), "italic"),
(style.underline.is_some(), "underline"),
(style.strikethrough.is_some(), "strikethrough"),
]
.into_iter()
.filter_map(|(set, name)| set.then_some(name))
.collect(),
Self::Paragraph(style) => [
(style.alignment.is_some(), "alignment"),
(style.named_style_type.is_some(), "namedStyleType"),
]
.into_iter()
.filter_map(|(set, name)| set.then_some(name))
.collect(),
};
fields.join(",")
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn masks_include_explicit_false_and_only_supplied_fields() {
let text = StylePatch::Text(TextStyle {
bold: Some(false),
underline: Some(true),
..TextStyle::default()
});
assert_eq!(text.fields(), "bold,underline");
assert_eq!(
serde_json::to_value(text).unwrap(),
serde_json::json!({"textStyle": {"bold": false, "underline": true}})
);
assert!(StylePatch::Text(TextStyle::default()).fields().is_empty());
assert!(StylePatch::Paragraph(ParagraphStyle::default())
.fields()
.is_empty());
assert_eq!(
StylePatch::Paragraph(ParagraphStyle {
alignment: None,
named_style_type: Some(NamedStyle::Title)
})
.fields(),
"namedStyleType"
);
}
#[test]
fn named_heading_styles_use_google_enum_spelling() {
for (i, style) in [
NamedStyle::Heading1,
NamedStyle::Heading2,
NamedStyle::Heading3,
NamedStyle::Heading4,
NamedStyle::Heading5,
NamedStyle::Heading6,
]
.into_iter()
.enumerate()
{
assert_eq!(
serde_json::to_value(style).unwrap(),
format!("HEADING_{}", i + 1)
);
}
}
}