use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct PostCount {
pub posts: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Tag {
pub id: String,
pub name: String,
pub slug: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feature_image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub visibility: 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 codeinjection_head: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub codeinjection_foot: Option<String>,
#[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 created_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<PostCount>,
}
impl Tag {
pub fn is_public(&self) -> bool {
matches!(self.visibility.as_deref(), None | Some("public"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_tag_minimal_deserialization() {
let json = json!({ "id": "1", "name": "Rust", "slug": "rust" });
let tag: Tag = serde_json::from_value(json).unwrap();
assert_eq!(tag.id, "1");
assert_eq!(tag.name, "Rust");
assert_eq!(tag.slug, "rust");
}
#[test]
fn test_tag_full_deserialization() {
let json = json!({
"id": "1",
"name": "Rust",
"slug": "rust",
"description": "Rust programming",
"feature_image": "https://example.com/img.jpg",
"visibility": "public",
"og_image": null,
"meta_title": "Rust Tag",
"url": "https://example.com/tag/rust/",
"created_at": "2021-01-01T00:00:00.000Z",
"updated_at": "2021-01-01T00:00:00.000Z",
"count": { "posts": 42 }
});
let tag: Tag = serde_json::from_value(json).unwrap();
assert_eq!(tag.description, Some("Rust programming".to_string()));
assert_eq!(tag.count, Some(PostCount { posts: 42 }));
assert!(tag.is_public());
}
#[test]
fn test_tag_is_public_with_public_visibility() {
let tag = Tag {
visibility: Some("public".to_string()),
..Default::default()
};
assert!(tag.is_public());
}
#[test]
fn test_tag_is_public_with_no_visibility() {
let tag = Tag::default();
assert!(tag.is_public());
}
#[test]
fn test_tag_is_not_public_when_internal() {
let tag = Tag {
visibility: Some("internal".to_string()),
..Default::default()
};
assert!(!tag.is_public());
}
#[test]
fn test_tag_serialization_skips_none() {
let tag = Tag {
id: "1".to_string(),
name: "T".to_string(),
slug: "t".to_string(),
..Default::default()
};
let json = serde_json::to_value(&tag).unwrap();
let obj = json.as_object().unwrap();
assert!(!obj.contains_key("description"));
assert!(!obj.contains_key("feature_image"));
assert!(!obj.contains_key("count"));
}
#[test]
fn test_post_count_default() {
let count = PostCount::default();
assert_eq!(count.posts, 0);
}
#[test]
fn test_post_count_serialization() {
let count = PostCount { posts: 10 };
let json = serde_json::to_value(&count).unwrap();
assert_eq!(json["posts"], 10);
}
#[test]
fn test_tag_clone_eq() {
let tag = Tag {
id: "1".to_string(),
name: "T".to_string(),
slug: "t".to_string(),
..Default::default()
};
assert_eq!(tag, tag.clone());
}
#[test]
fn test_tag_default() {
let tag = Tag::default();
assert!(tag.id.is_empty());
assert!(tag.name.is_empty());
assert!(tag.count.is_none());
}
}