use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::rest::{ResourceOperation, ResourcePath, RestResource};
use crate::HttpMethod;
use super::common::CollectionImage;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct CustomCollection {
#[serde(skip_serializing)]
pub id: Option<u64>,
#[serde(skip_serializing)]
pub handle: 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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_html: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_scope: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_order: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<CollectionImage>,
#[serde(skip_serializing)]
pub published: Option<bool>,
}
impl RestResource for CustomCollection {
type Id = u64;
type FindParams = CustomCollectionFindParams;
type AllParams = CustomCollectionListParams;
type CountParams = CustomCollectionCountParams;
const NAME: &'static str = "CustomCollection";
const PLURAL: &'static str = "custom_collections";
const PATHS: &'static [ResourcePath] = &[
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Find,
&["id"],
"custom_collections/{id}",
),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::All,
&[],
"custom_collections",
),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Count,
&[],
"custom_collections/count",
),
ResourcePath::new(
HttpMethod::Post,
ResourceOperation::Create,
&[],
"custom_collections",
),
ResourcePath::new(
HttpMethod::Put,
ResourceOperation::Update,
&["id"],
"custom_collections/{id}",
),
ResourcePath::new(
HttpMethod::Delete,
ResourceOperation::Delete,
&["id"],
"custom_collections/{id}",
),
];
fn get_id(&self) -> Option<Self::Id> {
self.id
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct CustomCollectionFindParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct CustomCollectionListParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<u64>>,
#[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 title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub product_id: Option<u64>,
#[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 published_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_info: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct CustomCollectionCountParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub product_id: Option<u64>,
#[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 published_status: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest::{get_path, ResourceOperation};
#[test]
fn test_custom_collection_struct_serialization() {
let collection = CustomCollection {
id: Some(841564295),
title: Some("Summer Collection".to_string()),
body_html: Some("<p>Best summer products</p>".to_string()),
handle: Some("summer-collection".to_string()),
published_at: None,
published_scope: Some("web".to_string()),
sort_order: Some("best-selling".to_string()),
template_suffix: Some("custom".to_string()),
image: Some(CollectionImage {
src: Some("https://cdn.shopify.com/collection.jpg".to_string()),
alt: Some("Summer".to_string()),
..Default::default()
}),
created_at: None,
updated_at: None,
admin_graphql_api_id: Some("gid://shopify/Collection/841564295".to_string()),
published: Some(true),
};
let json = serde_json::to_string(&collection).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["title"], "Summer Collection");
assert_eq!(parsed["body_html"], "<p>Best summer products</p>");
assert_eq!(parsed["published_scope"], "web");
assert_eq!(parsed["sort_order"], "best-selling");
assert_eq!(parsed["template_suffix"], "custom");
assert!(parsed.get("image").is_some());
assert!(parsed.get("id").is_none());
assert!(parsed.get("handle").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());
assert!(parsed.get("published").is_none());
}
#[test]
fn test_custom_collection_deserialization_from_api_response() {
let json = r#"{
"id": 841564295,
"handle": "ipods",
"title": "IPods",
"updated_at": "2024-01-02T09:28:43-05:00",
"body_html": "<p>The best iPods</p>",
"published_at": "2008-02-01T19:00:00-05:00",
"sort_order": "manual",
"template_suffix": null,
"published_scope": "web",
"admin_graphql_api_id": "gid://shopify/Collection/841564295",
"image": {
"src": "https://cdn.shopify.com/s/files/ipods.jpg",
"alt": "iPods collection",
"width": 1024,
"height": 768,
"created_at": "2024-01-01T10:00:00-05:00"
}
}"#;
let collection: CustomCollection = serde_json::from_str(json).unwrap();
assert_eq!(collection.id, Some(841564295));
assert_eq!(collection.handle.as_deref(), Some("ipods"));
assert_eq!(collection.title.as_deref(), Some("IPods"));
assert_eq!(
collection.body_html.as_deref(),
Some("<p>The best iPods</p>")
);
assert_eq!(collection.sort_order.as_deref(), Some("manual"));
assert_eq!(collection.published_scope.as_deref(), Some("web"));
assert!(collection.published_at.is_some());
assert!(collection.updated_at.is_some());
assert_eq!(
collection.admin_graphql_api_id.as_deref(),
Some("gid://shopify/Collection/841564295")
);
let image = collection.image.unwrap();
assert_eq!(
image.src.as_deref(),
Some("https://cdn.shopify.com/s/files/ipods.jpg")
);
assert_eq!(image.alt.as_deref(), Some("iPods collection"));
assert_eq!(image.width, Some(1024));
assert_eq!(image.height, Some(768));
}
#[test]
fn test_custom_collection_path_constants_are_correct() {
let find_path = get_path(CustomCollection::PATHS, ResourceOperation::Find, &["id"]);
assert!(find_path.is_some());
assert_eq!(find_path.unwrap().template, "custom_collections/{id}");
assert_eq!(find_path.unwrap().http_method, HttpMethod::Get);
let all_path = get_path(CustomCollection::PATHS, ResourceOperation::All, &[]);
assert!(all_path.is_some());
assert_eq!(all_path.unwrap().template, "custom_collections");
let count_path = get_path(CustomCollection::PATHS, ResourceOperation::Count, &[]);
assert!(count_path.is_some());
assert_eq!(count_path.unwrap().template, "custom_collections/count");
let create_path = get_path(CustomCollection::PATHS, ResourceOperation::Create, &[]);
assert!(create_path.is_some());
assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
let update_path = get_path(CustomCollection::PATHS, ResourceOperation::Update, &["id"]);
assert!(update_path.is_some());
assert_eq!(update_path.unwrap().http_method, HttpMethod::Put);
let delete_path = get_path(CustomCollection::PATHS, ResourceOperation::Delete, &["id"]);
assert!(delete_path.is_some());
assert_eq!(delete_path.unwrap().http_method, HttpMethod::Delete);
assert_eq!(CustomCollection::NAME, "CustomCollection");
assert_eq!(CustomCollection::PLURAL, "custom_collections");
}
#[test]
fn test_custom_collection_get_id_returns_correct_value() {
let collection_with_id = CustomCollection {
id: Some(841564295),
title: Some("Test Collection".to_string()),
..Default::default()
};
assert_eq!(collection_with_id.get_id(), Some(841564295));
let collection_without_id = CustomCollection {
id: None,
title: Some("New Collection".to_string()),
..Default::default()
};
assert_eq!(collection_without_id.get_id(), None);
}
#[test]
fn test_custom_collection_list_params_serialization() {
let params = CustomCollectionListParams {
ids: Some(vec![123, 456, 789]),
limit: Some(50),
since_id: Some(100),
title: Some("Summer".to_string()),
handle: Some("summer-sale".to_string()),
product_id: Some(999),
published_status: Some("published".to_string()),
..Default::default()
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["ids"], serde_json::json!([123, 456, 789]));
assert_eq!(json["limit"], 50);
assert_eq!(json["since_id"], 100);
assert_eq!(json["title"], "Summer");
assert_eq!(json["handle"], "summer-sale");
assert_eq!(json["product_id"], 999);
assert_eq!(json["published_status"], "published");
let empty_params = CustomCollectionListParams::default();
let empty_json = serde_json::to_value(&empty_params).unwrap();
assert_eq!(empty_json, serde_json::json!({}));
}
#[test]
fn test_collection_image_handling() {
let collection = CustomCollection {
title: Some("Image Test".to_string()),
image: Some(CollectionImage {
src: Some("https://example.com/image.jpg".to_string()),
alt: Some("Collection image".to_string()),
width: Some(800),
height: Some(600),
created_at: None,
}),
..Default::default()
};
let json = serde_json::to_string(&collection).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
let image = &parsed["image"];
assert_eq!(image["src"], "https://example.com/image.jpg");
assert_eq!(image["alt"], "Collection image");
assert_eq!(image["width"], 800);
assert_eq!(image["height"], 600);
assert!(image.get("created_at").is_none());
}
#[test]
fn test_sort_order_field() {
let sort_orders = vec![
"alpha-asc",
"alpha-desc",
"best-selling",
"created",
"created-desc",
"manual",
"price-asc",
"price-desc",
];
for sort_order in sort_orders {
let collection = CustomCollection {
title: Some("Test".to_string()),
sort_order: Some(sort_order.to_string()),
..Default::default()
};
let json = serde_json::to_string(&collection).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["sort_order"], sort_order);
}
}
#[test]
fn test_custom_collection_count_params_serialization() {
let params = CustomCollectionCountParams {
title: Some("Summer".to_string()),
product_id: Some(12345),
published_status: Some("published".to_string()),
..Default::default()
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["title"], "Summer");
assert_eq!(json["product_id"], 12345);
assert_eq!(json["published_status"], "published");
let empty_params = CustomCollectionCountParams::default();
let empty_json = serde_json::to_value(&empty_params).unwrap();
assert_eq!(empty_json, serde_json::json!({}));
}
}