use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct NavItem {
pub label: String,
pub url: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Settings {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logo: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub accent_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cover_image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub facebook: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub twitter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub codeinjection_head: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub codeinjection_foot: Option<String>,
#[serde(default)]
pub navigation: Vec<NavItem>,
#[serde(default)]
pub secondary_navigation: Vec<NavItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub og_image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub og_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub og_description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub twitter_image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub twitter_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub twitter_description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta_description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub members_support_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
impl Settings {
pub fn has_logo(&self) -> bool {
self.logo.is_some()
}
pub fn nav_count(&self) -> usize {
self.navigation.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_nav_item_deserialization() {
let json = json!({ "label": "Home", "url": "/" });
let item: NavItem = serde_json::from_value(json).unwrap();
assert_eq!(item.label, "Home");
assert_eq!(item.url, "/");
}
#[test]
fn test_settings_minimal_deserialization() {
let json = json!({ "title": "My Blog" });
let settings: Settings = serde_json::from_value(json).unwrap();
assert_eq!(settings.title, Some("My Blog".to_string()));
assert!(settings.navigation.is_empty());
}
#[test]
fn test_settings_full_deserialization() {
let json = json!({
"title": "Ghost",
"description": "Platform",
"logo": "https://example.com/logo.png",
"icon": "https://example.com/icon.png",
"accent_color": "#ff0095",
"cover_image": "https://example.com/cover.jpg",
"facebook": "ghostorg",
"twitter": "@Ghost",
"lang": "en",
"timezone": "Etc/UTC",
"navigation": [
{ "label": "Home", "url": "/" },
{ "label": "About", "url": "/about/" }
],
"secondary_navigation": [],
"url": "https://example.com/"
});
let settings: Settings = serde_json::from_value(json).unwrap();
assert_eq!(settings.title.as_deref(), Some("Ghost"));
assert_eq!(settings.lang.as_deref(), Some("en"));
assert_eq!(settings.navigation.len(), 2);
assert_eq!(settings.navigation[0].label, "Home");
assert!(settings.has_logo());
assert_eq!(settings.nav_count(), 2);
}
#[test]
fn test_settings_empty_navigation_default() {
let settings = Settings::default();
assert!(settings.navigation.is_empty());
assert_eq!(settings.nav_count(), 0);
}
#[test]
fn test_has_logo_true() {
let s = Settings {
logo: Some("https://example.com/logo.png".to_string()),
..Default::default()
};
assert!(s.has_logo());
}
#[test]
fn test_has_logo_false() {
assert!(!Settings::default().has_logo());
}
#[test]
fn test_settings_serialization_skips_none() {
let s = Settings {
title: Some("T".to_string()),
..Default::default()
};
let json = serde_json::to_value(&s).unwrap();
let obj = json.as_object().unwrap();
assert!(!obj.contains_key("logo"));
assert!(!obj.contains_key("twitter"));
}
#[test]
fn test_settings_clone_eq() {
let s = Settings {
title: Some("Blog".to_string()),
lang: Some("en".to_string()),
..Default::default()
};
assert_eq!(s, s.clone());
}
#[test]
fn test_nav_item_clone_eq() {
let item = NavItem {
label: "Home".to_string(),
url: "/".to_string(),
};
assert_eq!(item, item.clone());
}
#[test]
fn test_nav_item_default() {
let item = NavItem::default();
assert!(item.label.is_empty());
assert!(item.url.is_empty());
}
}