omni_dev/drive/docs/
style.rs1use serde::Serialize;
3
4#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
6pub struct TextStyle {
7 #[serde(skip_serializing_if = "Option::is_none")]
9 pub bold: Option<bool>,
10 #[serde(skip_serializing_if = "Option::is_none")]
12 pub italic: Option<bool>,
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub underline: Option<bool>,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub strikethrough: Option<bool>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, clap::ValueEnum)]
23#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
24pub enum Alignment {
25 Start,
27 Center,
29 End,
31 Justified,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, clap::ValueEnum)]
37#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
38pub enum NamedStyle {
39 NormalText,
41 Title,
43 Subtitle,
45 #[serde(rename = "HEADING_1")]
47 Heading1,
48 #[serde(rename = "HEADING_2")]
50 Heading2,
51 #[serde(rename = "HEADING_3")]
53 Heading3,
54 #[serde(rename = "HEADING_4")]
56 Heading4,
57 #[serde(rename = "HEADING_5")]
59 Heading5,
60 #[serde(rename = "HEADING_6")]
62 Heading6,
63}
64
65#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
67pub struct ParagraphStyle {
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub alignment: Option<Alignment>,
71 #[serde(rename = "namedStyleType", skip_serializing_if = "Option::is_none")]
73 pub named_style_type: Option<NamedStyle>,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
78pub enum StylePatch {
79 #[serde(rename = "textStyle")]
81 Text(TextStyle),
82 #[serde(rename = "paragraphStyle")]
84 Paragraph(ParagraphStyle),
85}
86
87impl StylePatch {
88 #[must_use]
90 pub fn fields(&self) -> String {
91 let fields: Vec<&str> = match self {
92 Self::Text(style) => [
93 (style.bold.is_some(), "bold"),
94 (style.italic.is_some(), "italic"),
95 (style.underline.is_some(), "underline"),
96 (style.strikethrough.is_some(), "strikethrough"),
97 ]
98 .into_iter()
99 .filter_map(|(set, name)| set.then_some(name))
100 .collect(),
101 Self::Paragraph(style) => [
102 (style.alignment.is_some(), "alignment"),
103 (style.named_style_type.is_some(), "namedStyleType"),
104 ]
105 .into_iter()
106 .filter_map(|(set, name)| set.then_some(name))
107 .collect(),
108 };
109 fields.join(",")
110 }
111}
112
113#[cfg(test)]
114#[allow(clippy::unwrap_used)]
115mod tests {
116 use super::*;
117 #[test]
118 fn masks_include_explicit_false_and_only_supplied_fields() {
119 let text = StylePatch::Text(TextStyle {
120 bold: Some(false),
121 underline: Some(true),
122 ..TextStyle::default()
123 });
124 assert_eq!(text.fields(), "bold,underline");
125 assert_eq!(
126 serde_json::to_value(text).unwrap(),
127 serde_json::json!({"textStyle": {"bold": false, "underline": true}})
128 );
129 assert!(StylePatch::Text(TextStyle::default()).fields().is_empty());
130 assert!(StylePatch::Paragraph(ParagraphStyle::default())
131 .fields()
132 .is_empty());
133 assert_eq!(
134 StylePatch::Paragraph(ParagraphStyle {
135 alignment: None,
136 named_style_type: Some(NamedStyle::Title)
137 })
138 .fields(),
139 "namedStyleType"
140 );
141 }
142 #[test]
143 fn named_heading_styles_use_google_enum_spelling() {
144 for (i, style) in [
145 NamedStyle::Heading1,
146 NamedStyle::Heading2,
147 NamedStyle::Heading3,
148 NamedStyle::Heading4,
149 NamedStyle::Heading5,
150 NamedStyle::Heading6,
151 ]
152 .into_iter()
153 .enumerate()
154 {
155 assert_eq!(
156 serde_json::to_value(style).unwrap(),
157 format!("HEADING_{}", i + 1)
158 );
159 }
160 }
161}