use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::rest::{ResourceOperation, ResourcePath, RestResource};
use crate::HttpMethod;
use super::common::BlogCommentable;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct Blog {
#[serde(skip_serializing)]
pub id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub commentable: Option<BlogCommentable>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feedburner: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feedburner_location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<String>,
#[serde(skip_serializing)]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub updated_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub admin_graphql_api_id: Option<String>,
}
impl RestResource for Blog {
type Id = u64;
type FindParams = BlogFindParams;
type AllParams = BlogListParams;
type CountParams = BlogCountParams;
const NAME: &'static str = "Blog";
const PLURAL: &'static str = "blogs";
const PATHS: &'static [ResourcePath] = &[
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Find,
&["id"],
"blogs/{id}",
),
ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "blogs"),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Count,
&[],
"blogs/count",
),
ResourcePath::new(HttpMethod::Post, ResourceOperation::Create, &[], "blogs"),
ResourcePath::new(
HttpMethod::Put,
ResourceOperation::Update,
&["id"],
"blogs/{id}",
),
ResourcePath::new(
HttpMethod::Delete,
ResourceOperation::Delete,
&["id"],
"blogs/{id}",
),
];
fn get_id(&self) -> Option<Self::Id> {
self.id
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct BlogFindParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct BlogListParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_info: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct BlogCountParams {
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest::{get_path, ResourceOperation};
#[test]
fn test_blog_struct_serialization() {
let blog = Blog {
id: Some(12345),
title: Some("Company News".to_string()),
handle: Some("news".to_string()),
commentable: Some(BlogCommentable::Moderate),
template_suffix: Some("custom".to_string()),
feedburner: Some("https://feeds.feedburner.com/example".to_string()),
feedburner_location: Some("example".to_string()),
tags: Some("news, updates".to_string()),
created_at: Some(
DateTime::parse_from_rfc3339("2024-01-10T08:00:00Z")
.unwrap()
.with_timezone(&Utc),
),
updated_at: Some(
DateTime::parse_from_rfc3339("2024-06-20T15:45:00Z")
.unwrap()
.with_timezone(&Utc),
),
admin_graphql_api_id: Some("gid://shopify/OnlineStoreBlog/12345".to_string()),
};
let json = serde_json::to_string(&blog).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["title"], "Company News");
assert_eq!(parsed["handle"], "news");
assert_eq!(parsed["commentable"], "moderate");
assert_eq!(parsed["template_suffix"], "custom");
assert_eq!(parsed["feedburner"], "https://feeds.feedburner.com/example");
assert_eq!(parsed["feedburner_location"], "example");
assert_eq!(parsed["tags"], "news, updates");
assert!(parsed.get("id").is_none());
assert!(parsed.get("created_at").is_none());
assert!(parsed.get("updated_at").is_none());
assert!(parsed.get("admin_graphql_api_id").is_none());
}
#[test]
fn test_blog_deserialization_from_api_response() {
let json = r#"{
"id": 241253187,
"handle": "apple-blog",
"title": "Apple Blog",
"updated_at": "2024-06-20T15:45:00Z",
"commentable": "no",
"feedburner": null,
"feedburner_location": null,
"created_at": "2024-01-10T08:00:00Z",
"template_suffix": null,
"tags": "apple, tech",
"admin_graphql_api_id": "gid://shopify/OnlineStoreBlog/241253187"
}"#;
let blog: Blog = serde_json::from_str(json).unwrap();
assert_eq!(blog.id, Some(241253187));
assert_eq!(blog.handle, Some("apple-blog".to_string()));
assert_eq!(blog.title, Some("Apple Blog".to_string()));
assert_eq!(blog.commentable, Some(BlogCommentable::No));
assert!(blog.feedburner.is_none());
assert!(blog.feedburner_location.is_none());
assert!(blog.template_suffix.is_none());
assert_eq!(blog.tags, Some("apple, tech".to_string()));
assert!(blog.created_at.is_some());
assert!(blog.updated_at.is_some());
assert_eq!(
blog.admin_graphql_api_id,
Some("gid://shopify/OnlineStoreBlog/241253187".to_string())
);
}
#[test]
fn test_blog_commentable_enum_handling() {
let variants = [
(BlogCommentable::No, "no"),
(BlogCommentable::Moderate, "moderate"),
(BlogCommentable::Yes, "yes"),
];
for (commentable, expected_str) in variants {
let blog = Blog {
title: Some("Test Blog".to_string()),
commentable: Some(commentable),
..Default::default()
};
let json = serde_json::to_value(&blog).unwrap();
assert_eq!(json["commentable"], expected_str);
}
}
#[test]
fn test_blog_list_params_serialization() {
let params = BlogListParams {
handle: Some("news".to_string()),
limit: Some(50),
since_id: Some(100),
..Default::default()
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["handle"], "news");
assert_eq!(json["limit"], 50);
assert_eq!(json["since_id"], 100);
assert!(json.get("created_at_min").is_none());
assert!(json.get("page_info").is_none());
}
#[test]
fn test_blog_path_constants_are_correct() {
let find_path = get_path(Blog::PATHS, ResourceOperation::Find, &["id"]);
assert!(find_path.is_some());
assert_eq!(find_path.unwrap().template, "blogs/{id}");
assert_eq!(find_path.unwrap().http_method, HttpMethod::Get);
let all_path = get_path(Blog::PATHS, ResourceOperation::All, &[]);
assert!(all_path.is_some());
assert_eq!(all_path.unwrap().template, "blogs");
assert_eq!(all_path.unwrap().http_method, HttpMethod::Get);
let count_path = get_path(Blog::PATHS, ResourceOperation::Count, &[]);
assert!(count_path.is_some());
assert_eq!(count_path.unwrap().template, "blogs/count");
assert_eq!(count_path.unwrap().http_method, HttpMethod::Get);
let create_path = get_path(Blog::PATHS, ResourceOperation::Create, &[]);
assert!(create_path.is_some());
assert_eq!(create_path.unwrap().template, "blogs");
assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
let update_path = get_path(Blog::PATHS, ResourceOperation::Update, &["id"]);
assert!(update_path.is_some());
assert_eq!(update_path.unwrap().template, "blogs/{id}");
assert_eq!(update_path.unwrap().http_method, HttpMethod::Put);
let delete_path = get_path(Blog::PATHS, ResourceOperation::Delete, &["id"]);
assert!(delete_path.is_some());
assert_eq!(delete_path.unwrap().template, "blogs/{id}");
assert_eq!(delete_path.unwrap().http_method, HttpMethod::Delete);
assert_eq!(Blog::NAME, "Blog");
assert_eq!(Blog::PLURAL, "blogs");
}
#[test]
fn test_blog_get_id_returns_correct_value() {
let blog_with_id = Blog {
id: Some(123456789),
title: Some("Test Blog".to_string()),
..Default::default()
};
assert_eq!(blog_with_id.get_id(), Some(123456789));
let blog_without_id = Blog {
id: None,
title: Some("New Blog".to_string()),
..Default::default()
};
assert_eq!(blog_without_id.get_id(), None);
}
#[test]
fn test_blog_tags_field_handling() {
let blog = Blog {
title: Some("Tech Blog".to_string()),
tags: Some("tech, programming, rust, web".to_string()),
..Default::default()
};
let json = serde_json::to_value(&blog).unwrap();
assert_eq!(json["tags"], "tech, programming, rust, web");
let deserialized: Blog = serde_json::from_value(json).unwrap();
assert_eq!(
deserialized.tags,
Some("tech, programming, rust, web".to_string())
);
}
}