ghost_io_api/models/
settings.rs1use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
35pub struct NavItem {
36 pub label: String,
38 pub url: String,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
70pub struct Settings {
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub title: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub description: Option<String>,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub logo: Option<String>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub icon: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub accent_color: Option<String>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub cover_image: Option<String>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub facebook: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub twitter: Option<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub lang: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub timezone: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub codeinjection_head: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub codeinjection_foot: Option<String>,
107 #[serde(default)]
109 pub navigation: Vec<NavItem>,
110 #[serde(default)]
112 pub secondary_navigation: Vec<NavItem>,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub og_image: Option<String>,
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub og_title: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub og_description: Option<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub twitter_image: Option<String>,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub twitter_title: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub twitter_description: Option<String>,
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub meta_title: Option<String>,
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub meta_description: Option<String>,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub members_support_address: Option<String>,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub url: Option<String>,
143}
144
145impl Settings {
146 pub fn has_logo(&self) -> bool {
160 self.logo.is_some()
161 }
162
163 pub fn nav_count(&self) -> usize {
179 self.navigation.len()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use serde_json::json;
187
188 #[test]
189 fn test_nav_item_deserialization() {
190 let json = json!({ "label": "Home", "url": "/" });
191 let item: NavItem = serde_json::from_value(json).unwrap();
192 assert_eq!(item.label, "Home");
193 assert_eq!(item.url, "/");
194 }
195
196 #[test]
197 fn test_settings_minimal_deserialization() {
198 let json = json!({ "title": "My Blog" });
199 let settings: Settings = serde_json::from_value(json).unwrap();
200 assert_eq!(settings.title, Some("My Blog".to_string()));
201 assert!(settings.navigation.is_empty());
202 }
203
204 #[test]
205 fn test_settings_full_deserialization() {
206 let json = json!({
207 "title": "Ghost",
208 "description": "Platform",
209 "logo": "https://example.com/logo.png",
210 "icon": "https://example.com/icon.png",
211 "accent_color": "#ff0095",
212 "cover_image": "https://example.com/cover.jpg",
213 "facebook": "ghostorg",
214 "twitter": "@Ghost",
215 "lang": "en",
216 "timezone": "Etc/UTC",
217 "navigation": [
218 { "label": "Home", "url": "/" },
219 { "label": "About", "url": "/about/" }
220 ],
221 "secondary_navigation": [],
222 "url": "https://example.com/"
223 });
224 let settings: Settings = serde_json::from_value(json).unwrap();
225 assert_eq!(settings.title.as_deref(), Some("Ghost"));
226 assert_eq!(settings.lang.as_deref(), Some("en"));
227 assert_eq!(settings.navigation.len(), 2);
228 assert_eq!(settings.navigation[0].label, "Home");
229 assert!(settings.has_logo());
230 assert_eq!(settings.nav_count(), 2);
231 }
232
233 #[test]
234 fn test_settings_empty_navigation_default() {
235 let settings = Settings::default();
236 assert!(settings.navigation.is_empty());
237 assert_eq!(settings.nav_count(), 0);
238 }
239
240 #[test]
241 fn test_has_logo_true() {
242 let s = Settings {
243 logo: Some("https://example.com/logo.png".to_string()),
244 ..Default::default()
245 };
246 assert!(s.has_logo());
247 }
248
249 #[test]
250 fn test_has_logo_false() {
251 assert!(!Settings::default().has_logo());
252 }
253
254 #[test]
255 fn test_settings_serialization_skips_none() {
256 let s = Settings {
257 title: Some("T".to_string()),
258 ..Default::default()
259 };
260 let json = serde_json::to_value(&s).unwrap();
261 let obj = json.as_object().unwrap();
262 assert!(!obj.contains_key("logo"));
263 assert!(!obj.contains_key("twitter"));
264 }
265
266 #[test]
267 fn test_settings_clone_eq() {
268 let s = Settings {
269 title: Some("Blog".to_string()),
270 lang: Some("en".to_string()),
271 ..Default::default()
272 };
273 assert_eq!(s, s.clone());
274 }
275
276 #[test]
277 fn test_nav_item_clone_eq() {
278 let item = NavItem {
279 label: "Home".to_string(),
280 url: "/".to_string(),
281 };
282 assert_eq!(item, item.clone());
283 }
284
285 #[test]
286 fn test_nav_item_default() {
287 let item = NavItem::default();
288 assert!(item.label.is_empty());
289 assert!(item.url.is_empty());
290 }
291}