ghost_io_api/models/
tag.rs1use serde::{Deserialize, Serialize};
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
28pub struct PostCount {
29 pub posts: u32,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
51pub struct Tag {
52 pub id: String,
54 pub name: String,
56 pub slug: String,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub description: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub feature_image: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub visibility: Option<String>,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub og_image: Option<String>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub og_title: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub og_description: Option<String>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub twitter_image: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub twitter_title: Option<String>,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub twitter_description: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub meta_title: Option<String>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub meta_description: Option<String>,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub codeinjection_head: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub codeinjection_foot: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub canonical_url: Option<String>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub url: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub created_at: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub updated_at: Option<String>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub count: Option<PostCount>,
112}
113
114impl Tag {
115 pub fn is_public(&self) -> bool {
129 matches!(self.visibility.as_deref(), None | Some("public"))
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use serde_json::json;
137
138 #[test]
139 fn test_tag_minimal_deserialization() {
140 let json = json!({ "id": "1", "name": "Rust", "slug": "rust" });
141 let tag: Tag = serde_json::from_value(json).unwrap();
142 assert_eq!(tag.id, "1");
143 assert_eq!(tag.name, "Rust");
144 assert_eq!(tag.slug, "rust");
145 }
146
147 #[test]
148 fn test_tag_full_deserialization() {
149 let json = json!({
150 "id": "1",
151 "name": "Rust",
152 "slug": "rust",
153 "description": "Rust programming",
154 "feature_image": "https://example.com/img.jpg",
155 "visibility": "public",
156 "og_image": null,
157 "meta_title": "Rust Tag",
158 "url": "https://example.com/tag/rust/",
159 "created_at": "2021-01-01T00:00:00.000Z",
160 "updated_at": "2021-01-01T00:00:00.000Z",
161 "count": { "posts": 42 }
162 });
163 let tag: Tag = serde_json::from_value(json).unwrap();
164 assert_eq!(tag.description, Some("Rust programming".to_string()));
165 assert_eq!(tag.count, Some(PostCount { posts: 42 }));
166 assert!(tag.is_public());
167 }
168
169 #[test]
170 fn test_tag_is_public_with_public_visibility() {
171 let tag = Tag {
172 visibility: Some("public".to_string()),
173 ..Default::default()
174 };
175 assert!(tag.is_public());
176 }
177
178 #[test]
179 fn test_tag_is_public_with_no_visibility() {
180 let tag = Tag::default();
181 assert!(tag.is_public());
182 }
183
184 #[test]
185 fn test_tag_is_not_public_when_internal() {
186 let tag = Tag {
187 visibility: Some("internal".to_string()),
188 ..Default::default()
189 };
190 assert!(!tag.is_public());
191 }
192
193 #[test]
194 fn test_tag_serialization_skips_none() {
195 let tag = Tag {
196 id: "1".to_string(),
197 name: "T".to_string(),
198 slug: "t".to_string(),
199 ..Default::default()
200 };
201 let json = serde_json::to_value(&tag).unwrap();
202 let obj = json.as_object().unwrap();
203 assert!(!obj.contains_key("description"));
204 assert!(!obj.contains_key("feature_image"));
205 assert!(!obj.contains_key("count"));
206 }
207
208 #[test]
209 fn test_post_count_default() {
210 let count = PostCount::default();
211 assert_eq!(count.posts, 0);
212 }
213
214 #[test]
215 fn test_post_count_serialization() {
216 let count = PostCount { posts: 10 };
217 let json = serde_json::to_value(&count).unwrap();
218 assert_eq!(json["posts"], 10);
219 }
220
221 #[test]
222 fn test_tag_clone_eq() {
223 let tag = Tag {
224 id: "1".to_string(),
225 name: "T".to_string(),
226 slug: "t".to_string(),
227 ..Default::default()
228 };
229 assert_eq!(tag, tag.clone());
230 }
231
232 #[test]
233 fn test_tag_default() {
234 let tag = Tag::default();
235 assert!(tag.id.is_empty());
236 assert!(tag.name.is_empty());
237 assert!(tag.count.is_none());
238 }
239}