use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum BlogIdentifier {
Name(String),
Hostname(String),
Uuid(String),
}
impl BlogIdentifier {
pub fn as_str(&self) -> &str {
match self {
BlogIdentifier::Name(s) | BlogIdentifier::Hostname(s) | BlogIdentifier::Uuid(s) => s,
}
}
}
impl fmt::Display for BlogIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<String> for BlogIdentifier {
fn from(s: String) -> Self {
if s.starts_with("t:") {
BlogIdentifier::Uuid(s)
} else if s.contains('.') {
BlogIdentifier::Hostname(s)
} else {
BlogIdentifier::Name(s)
}
}
}
impl From<&str> for BlogIdentifier {
fn from(s: &str) -> Self {
BlogIdentifier::from(s.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Blog {
pub name: String,
pub title: String,
#[serde(default)]
pub description: String,
pub url: String,
pub uuid: String,
pub updated: i64,
#[serde(default)]
pub posts: u64,
#[serde(default)]
pub total_posts: u64,
#[serde(default)]
pub is_nsfw: bool,
#[serde(default)]
pub can_be_followed: bool,
#[serde(default)]
pub followed: bool,
#[serde(default)]
pub ask: bool,
#[serde(default)]
pub ask_anon: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub ask_page_title: Option<String>,
#[serde(default)]
pub asks_allow_media: bool,
#[serde(default)]
pub can_chat: bool,
#[serde(default)]
pub can_send_fan_mail: bool,
#[serde(default)]
pub can_subscribe: bool,
#[serde(default)]
pub subscribed: bool,
#[serde(default)]
pub share_likes: bool,
#[serde(default)]
pub share_following: bool,
#[serde(default)]
pub avatar: Vec<AvatarImage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<Theme>,
#[serde(default)]
pub theme_id: u64,
#[serde(default, deserialize_with = "crate::empty_object_as_none")]
pub tumblrmart_accessories: Option<TumblrmartAccessories>,
#[serde(default)]
pub can_show_badges: bool,
#[serde(default)]
pub is_blocked_from_primary: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AvatarImage {
pub width: u32,
pub height: u32,
pub url: String,
#[serde(default)]
pub accessories: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Theme {
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar_shape: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub background_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_font: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_bounds: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_image_focused: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_image_poster: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_image_scaled: Option<String>,
#[serde(default)]
pub header_stretch: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub link_color: Option<String>,
#[serde(default)]
pub show_avatar: bool,
#[serde(default)]
pub show_description: bool,
#[serde(default)]
pub show_header_image: bool,
#[serde(default)]
pub show_title: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub title_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title_font: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title_font_weight: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_full_width: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_full_height: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_focus_width: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_focus_height: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub name: String,
#[serde(default)]
pub likes: u64,
#[serde(default)]
pub following: u64,
#[serde(default)]
pub blogs: Vec<Blog>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Page<T> {
#[serde(flatten)]
pub items: Vec<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next: Option<String>,
}
impl<T> Page<T> {
pub fn new(items: Vec<T>) -> Self {
Self {
items,
total: None,
next: None,
}
}
pub fn with_total(items: Vec<T>, total: u64) -> Self {
Self {
items,
total: Some(total),
next: None,
}
}
pub fn has_next(&self) -> bool {
self.next.is_some()
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl<T> Default for Page<T> {
fn default() -> Self {
Self {
items: Vec::new(),
total: None,
next: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TumblrmartAccessories {
#[serde(default)]
pub badges: Vec<Badge>,
#[serde(default)]
pub blue_checkmark_count: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Badge {
pub product_group: String,
#[serde(default)]
pub urls: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination_url: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blog_identifier_from_name() {
let id = BlogIdentifier::from("staff");
assert_eq!(id, BlogIdentifier::Name("staff".to_string()));
assert_eq!(id.as_str(), "staff");
}
#[test]
fn test_blog_identifier_from_hostname() {
let id = BlogIdentifier::from("staff.tumblr.com");
assert_eq!(id, BlogIdentifier::Hostname("staff.tumblr.com".to_string()));
assert_eq!(id.as_str(), "staff.tumblr.com");
}
#[test]
fn test_blog_identifier_from_uuid() {
let id = BlogIdentifier::from("t:123456789");
assert_eq!(id, BlogIdentifier::Uuid("t:123456789".to_string()));
assert_eq!(id.as_str(), "t:123456789");
}
#[test]
fn test_blog_identifier_display() {
let id = BlogIdentifier::Name("staff".to_string());
assert_eq!(format!("{}", id), "staff");
}
#[test]
fn test_page_creation() {
let page = Page::new(vec![1, 2, 3]);
assert_eq!(page.len(), 3);
assert!(!page.has_next());
}
#[test]
fn test_page_with_total() {
let page = Page::with_total(vec![1, 2, 3], 100);
assert_eq!(page.total, Some(100));
}
#[test]
fn test_page_has_next() {
let mut page = Page::new(vec![1, 2, 3]);
assert!(!page.has_next());
page.next = Some("next_url".to_string());
assert!(page.has_next());
}
#[test]
fn test_page_is_empty() {
let empty_page: Page<i32> = Page::default();
assert!(empty_page.is_empty());
let page = Page::new(vec![1]);
assert!(!page.is_empty());
}
}