use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum PageStatus {
#[default]
Draft,
Published,
Scheduled,
}
impl PageStatus {
pub fn is_published(&self) -> bool {
matches!(self, PageStatus::Published)
}
pub fn is_draft(&self) -> bool {
matches!(self, PageStatus::Draft)
}
pub fn is_scheduled(&self) -> bool {
matches!(self, PageStatus::Scheduled)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Page {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
pub title: String,
pub slug: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub html: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plaintext: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feature_image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feature_image_alt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feature_image_caption: Option<String>,
#[serde(default)]
pub featured: bool,
#[serde(default)]
pub status: PageStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_excerpt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub excerpt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reading_time: Option<u32>,
#[serde(default)]
pub access: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub canonical_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[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 custom_template: Option<String>,
}
impl Page {
pub fn is_published(&self) -> bool {
self.status.is_published()
}
pub fn is_draft(&self) -> bool {
self.status.is_draft()
}
pub fn is_scheduled(&self) -> bool {
self.status.is_scheduled()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_page_status_default() {
assert_eq!(PageStatus::default(), PageStatus::Draft);
}
#[test]
fn test_page_status_is_published() {
assert!(PageStatus::Published.is_published());
assert!(!PageStatus::Draft.is_published());
assert!(!PageStatus::Scheduled.is_published());
}
#[test]
fn test_page_status_is_draft() {
assert!(PageStatus::Draft.is_draft());
assert!(!PageStatus::Published.is_draft());
}
#[test]
fn test_page_status_is_scheduled() {
assert!(PageStatus::Scheduled.is_scheduled());
assert!(!PageStatus::Published.is_scheduled());
}
#[test]
fn test_page_status_serialization() {
assert_eq!(
serde_json::to_string(&PageStatus::Published).unwrap(),
"\"published\""
);
assert_eq!(
serde_json::to_string(&PageStatus::Draft).unwrap(),
"\"draft\""
);
assert_eq!(
serde_json::to_string(&PageStatus::Scheduled).unwrap(),
"\"scheduled\""
);
}
#[test]
fn test_page_status_deserialization() {
let s: PageStatus = serde_json::from_str("\"published\"").unwrap();
assert_eq!(s, PageStatus::Published);
let d: PageStatus = serde_json::from_str("\"draft\"").unwrap();
assert_eq!(d, PageStatus::Draft);
}
#[test]
fn test_page_minimal_deserialization() {
let json = json!({
"id": "abc123",
"title": "About",
"slug": "about"
});
let page: Page = serde_json::from_value(json).unwrap();
assert_eq!(page.id, "abc123");
assert_eq!(page.title, "About");
assert_eq!(page.slug, "about");
assert_eq!(page.status, PageStatus::Draft);
assert!(!page.featured);
}
#[test]
fn test_page_full_deserialization() {
let json = json!({
"id": "abc123",
"uuid": "a-b-c-d",
"title": "About",
"slug": "about",
"html": "<p>Hello</p>",
"plaintext": "Hello",
"feature_image": "https://example.com/img.jpg",
"featured": false,
"status": "published",
"visibility": "public",
"created_at": "2021-01-01T00:00:00.000Z",
"updated_at": "2021-01-02T00:00:00.000Z",
"published_at": "2021-01-02T00:00:00.000Z",
"custom_excerpt": "Custom",
"excerpt": "Auto",
"reading_time": 2,
"access": true,
"url": "https://example.com/about/"
});
let page: Page = serde_json::from_value(json).unwrap();
assert_eq!(page.status, PageStatus::Published);
assert!(page.is_published());
assert_eq!(page.reading_time, Some(2));
assert!(page.access);
}
#[test]
fn test_page_default() {
let page = Page::default();
assert_eq!(page.status, PageStatus::Draft);
assert!(page.id.is_empty());
assert!(!page.featured);
assert!(!page.access);
}
#[test]
fn test_page_is_methods() {
let published = Page {
status: PageStatus::Published,
..Default::default()
};
assert!(published.is_published());
assert!(!published.is_draft());
assert!(!published.is_scheduled());
let draft = Page {
status: PageStatus::Draft,
..Default::default()
};
assert!(draft.is_draft());
let scheduled = Page {
status: PageStatus::Scheduled,
..Default::default()
};
assert!(scheduled.is_scheduled());
}
#[test]
fn test_page_serialization_skips_none() {
let page = Page {
id: "1".to_string(),
title: "T".to_string(),
slug: "t".to_string(),
..Default::default()
};
let json = serde_json::to_value(&page).unwrap();
let obj = json.as_object().unwrap();
assert!(!obj.contains_key("html"));
assert!(!obj.contains_key("feature_image"));
assert!(!obj.contains_key("og_image"));
}
#[test]
fn test_page_clone_eq() {
let page = Page {
id: "1".to_string(),
title: "T".to_string(),
slug: "t".to_string(),
status: PageStatus::Published,
..Default::default()
};
assert_eq!(page, page.clone());
}
}