1use chrono::{DateTime, Utc};
37use serde::{Deserialize, Serialize};
38
39use crate::rest::{ResourceOperation, ResourcePath, RestResource};
40use crate::HttpMethod;
41
42use super::common::CollectionImage;
43
44#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
81pub struct CustomCollection {
82 #[serde(skip_serializing)]
85 pub id: Option<u64>,
86
87 #[serde(skip_serializing)]
90 pub handle: Option<String>,
91
92 #[serde(skip_serializing)]
94 pub created_at: Option<DateTime<Utc>>,
95
96 #[serde(skip_serializing)]
98 pub updated_at: Option<DateTime<Utc>>,
99
100 #[serde(skip_serializing)]
102 pub admin_graphql_api_id: Option<String>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
107 pub title: Option<String>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub body_html: Option<String>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
116 pub published_at: Option<DateTime<Utc>>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
121 pub published_scope: Option<String>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
135 pub sort_order: Option<String>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub template_suffix: Option<String>,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub image: Option<CollectionImage>,
144
145 #[serde(skip_serializing)]
148 pub published: Option<bool>,
149}
150
151impl RestResource for CustomCollection {
152 type Id = u64;
153 type FindParams = CustomCollectionFindParams;
154 type AllParams = CustomCollectionListParams;
155 type CountParams = CustomCollectionCountParams;
156
157 const NAME: &'static str = "CustomCollection";
158 const PLURAL: &'static str = "custom_collections";
159
160 const PATHS: &'static [ResourcePath] = &[
161 ResourcePath::new(
162 HttpMethod::Get,
163 ResourceOperation::Find,
164 &["id"],
165 "custom_collections/{id}",
166 ),
167 ResourcePath::new(
168 HttpMethod::Get,
169 ResourceOperation::All,
170 &[],
171 "custom_collections",
172 ),
173 ResourcePath::new(
174 HttpMethod::Get,
175 ResourceOperation::Count,
176 &[],
177 "custom_collections/count",
178 ),
179 ResourcePath::new(
180 HttpMethod::Post,
181 ResourceOperation::Create,
182 &[],
183 "custom_collections",
184 ),
185 ResourcePath::new(
186 HttpMethod::Put,
187 ResourceOperation::Update,
188 &["id"],
189 "custom_collections/{id}",
190 ),
191 ResourcePath::new(
192 HttpMethod::Delete,
193 ResourceOperation::Delete,
194 &["id"],
195 "custom_collections/{id}",
196 ),
197 ];
198
199 fn get_id(&self) -> Option<Self::Id> {
200 self.id
201 }
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
206pub struct CustomCollectionFindParams {
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub fields: Option<String>,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
214pub struct CustomCollectionListParams {
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub ids: Option<Vec<u64>>,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub limit: Option<u32>,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub since_id: Option<u64>,
226
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub title: Option<String>,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub handle: Option<String>,
234
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub product_id: Option<u64>,
238
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub updated_at_min: Option<DateTime<Utc>>,
242
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub updated_at_max: Option<DateTime<Utc>>,
246
247 #[serde(skip_serializing_if = "Option::is_none")]
250 pub published_status: Option<String>,
251
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub fields: Option<String>,
255
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub page_info: Option<String>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
263pub struct CustomCollectionCountParams {
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub title: Option<String>,
267
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub product_id: Option<u64>,
271
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub updated_at_min: Option<DateTime<Utc>>,
275
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub updated_at_max: Option<DateTime<Utc>>,
279
280 #[serde(skip_serializing_if = "Option::is_none")]
283 pub published_status: Option<String>,
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::rest::{get_path, ResourceOperation};
290
291 #[test]
292 fn test_custom_collection_struct_serialization() {
293 let collection = CustomCollection {
294 id: Some(841564295),
295 title: Some("Summer Collection".to_string()),
296 body_html: Some("<p>Best summer products</p>".to_string()),
297 handle: Some("summer-collection".to_string()),
298 published_at: None,
299 published_scope: Some("web".to_string()),
300 sort_order: Some("best-selling".to_string()),
301 template_suffix: Some("custom".to_string()),
302 image: Some(CollectionImage {
303 src: Some("https://cdn.shopify.com/collection.jpg".to_string()),
304 alt: Some("Summer".to_string()),
305 ..Default::default()
306 }),
307 created_at: None,
308 updated_at: None,
309 admin_graphql_api_id: Some("gid://shopify/Collection/841564295".to_string()),
310 published: Some(true),
311 };
312
313 let json = serde_json::to_string(&collection).unwrap();
314 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
315
316 assert_eq!(parsed["title"], "Summer Collection");
318 assert_eq!(parsed["body_html"], "<p>Best summer products</p>");
319 assert_eq!(parsed["published_scope"], "web");
320 assert_eq!(parsed["sort_order"], "best-selling");
321 assert_eq!(parsed["template_suffix"], "custom");
322 assert!(parsed.get("image").is_some());
323
324 assert!(parsed.get("id").is_none());
326 assert!(parsed.get("handle").is_none());
327 assert!(parsed.get("created_at").is_none());
328 assert!(parsed.get("updated_at").is_none());
329 assert!(parsed.get("admin_graphql_api_id").is_none());
330 assert!(parsed.get("published").is_none());
331 }
332
333 #[test]
334 fn test_custom_collection_deserialization_from_api_response() {
335 let json = r#"{
336 "id": 841564295,
337 "handle": "ipods",
338 "title": "IPods",
339 "updated_at": "2024-01-02T09:28:43-05:00",
340 "body_html": "<p>The best iPods</p>",
341 "published_at": "2008-02-01T19:00:00-05:00",
342 "sort_order": "manual",
343 "template_suffix": null,
344 "published_scope": "web",
345 "admin_graphql_api_id": "gid://shopify/Collection/841564295",
346 "image": {
347 "src": "https://cdn.shopify.com/s/files/ipods.jpg",
348 "alt": "iPods collection",
349 "width": 1024,
350 "height": 768,
351 "created_at": "2024-01-01T10:00:00-05:00"
352 }
353 }"#;
354
355 let collection: CustomCollection = serde_json::from_str(json).unwrap();
356
357 assert_eq!(collection.id, Some(841564295));
358 assert_eq!(collection.handle.as_deref(), Some("ipods"));
359 assert_eq!(collection.title.as_deref(), Some("IPods"));
360 assert_eq!(
361 collection.body_html.as_deref(),
362 Some("<p>The best iPods</p>")
363 );
364 assert_eq!(collection.sort_order.as_deref(), Some("manual"));
365 assert_eq!(collection.published_scope.as_deref(), Some("web"));
366 assert!(collection.published_at.is_some());
367 assert!(collection.updated_at.is_some());
368 assert_eq!(
369 collection.admin_graphql_api_id.as_deref(),
370 Some("gid://shopify/Collection/841564295")
371 );
372
373 let image = collection.image.unwrap();
375 assert_eq!(
376 image.src.as_deref(),
377 Some("https://cdn.shopify.com/s/files/ipods.jpg")
378 );
379 assert_eq!(image.alt.as_deref(), Some("iPods collection"));
380 assert_eq!(image.width, Some(1024));
381 assert_eq!(image.height, Some(768));
382 }
383
384 #[test]
385 fn test_custom_collection_path_constants_are_correct() {
386 let find_path = get_path(CustomCollection::PATHS, ResourceOperation::Find, &["id"]);
388 assert!(find_path.is_some());
389 assert_eq!(find_path.unwrap().template, "custom_collections/{id}");
390 assert_eq!(find_path.unwrap().http_method, HttpMethod::Get);
391
392 let all_path = get_path(CustomCollection::PATHS, ResourceOperation::All, &[]);
394 assert!(all_path.is_some());
395 assert_eq!(all_path.unwrap().template, "custom_collections");
396
397 let count_path = get_path(CustomCollection::PATHS, ResourceOperation::Count, &[]);
399 assert!(count_path.is_some());
400 assert_eq!(count_path.unwrap().template, "custom_collections/count");
401
402 let create_path = get_path(CustomCollection::PATHS, ResourceOperation::Create, &[]);
404 assert!(create_path.is_some());
405 assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
406
407 let update_path = get_path(CustomCollection::PATHS, ResourceOperation::Update, &["id"]);
409 assert!(update_path.is_some());
410 assert_eq!(update_path.unwrap().http_method, HttpMethod::Put);
411
412 let delete_path = get_path(CustomCollection::PATHS, ResourceOperation::Delete, &["id"]);
414 assert!(delete_path.is_some());
415 assert_eq!(delete_path.unwrap().http_method, HttpMethod::Delete);
416
417 assert_eq!(CustomCollection::NAME, "CustomCollection");
419 assert_eq!(CustomCollection::PLURAL, "custom_collections");
420 }
421
422 #[test]
423 fn test_custom_collection_get_id_returns_correct_value() {
424 let collection_with_id = CustomCollection {
425 id: Some(841564295),
426 title: Some("Test Collection".to_string()),
427 ..Default::default()
428 };
429 assert_eq!(collection_with_id.get_id(), Some(841564295));
430
431 let collection_without_id = CustomCollection {
432 id: None,
433 title: Some("New Collection".to_string()),
434 ..Default::default()
435 };
436 assert_eq!(collection_without_id.get_id(), None);
437 }
438
439 #[test]
440 fn test_custom_collection_list_params_serialization() {
441 let params = CustomCollectionListParams {
442 ids: Some(vec![123, 456, 789]),
443 limit: Some(50),
444 since_id: Some(100),
445 title: Some("Summer".to_string()),
446 handle: Some("summer-sale".to_string()),
447 product_id: Some(999),
448 published_status: Some("published".to_string()),
449 ..Default::default()
450 };
451
452 let json = serde_json::to_value(¶ms).unwrap();
453
454 assert_eq!(json["ids"], serde_json::json!([123, 456, 789]));
455 assert_eq!(json["limit"], 50);
456 assert_eq!(json["since_id"], 100);
457 assert_eq!(json["title"], "Summer");
458 assert_eq!(json["handle"], "summer-sale");
459 assert_eq!(json["product_id"], 999);
460 assert_eq!(json["published_status"], "published");
461
462 let empty_params = CustomCollectionListParams::default();
464 let empty_json = serde_json::to_value(&empty_params).unwrap();
465 assert_eq!(empty_json, serde_json::json!({}));
466 }
467
468 #[test]
469 fn test_collection_image_handling() {
470 let collection = CustomCollection {
472 title: Some("Image Test".to_string()),
473 image: Some(CollectionImage {
474 src: Some("https://example.com/image.jpg".to_string()),
475 alt: Some("Collection image".to_string()),
476 width: Some(800),
477 height: Some(600),
478 created_at: None,
479 }),
480 ..Default::default()
481 };
482
483 let json = serde_json::to_string(&collection).unwrap();
484 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
485
486 let image = &parsed["image"];
487 assert_eq!(image["src"], "https://example.com/image.jpg");
488 assert_eq!(image["alt"], "Collection image");
489 assert_eq!(image["width"], 800);
490 assert_eq!(image["height"], 600);
491 assert!(image.get("created_at").is_none());
493 }
494
495 #[test]
496 fn test_sort_order_field() {
497 let sort_orders = vec![
499 "alpha-asc",
500 "alpha-desc",
501 "best-selling",
502 "created",
503 "created-desc",
504 "manual",
505 "price-asc",
506 "price-desc",
507 ];
508
509 for sort_order in sort_orders {
510 let collection = CustomCollection {
511 title: Some("Test".to_string()),
512 sort_order: Some(sort_order.to_string()),
513 ..Default::default()
514 };
515
516 let json = serde_json::to_string(&collection).unwrap();
517 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
518
519 assert_eq!(parsed["sort_order"], sort_order);
520 }
521 }
522
523 #[test]
524 fn test_custom_collection_count_params_serialization() {
525 let params = CustomCollectionCountParams {
526 title: Some("Summer".to_string()),
527 product_id: Some(12345),
528 published_status: Some("published".to_string()),
529 ..Default::default()
530 };
531
532 let json = serde_json::to_value(¶ms).unwrap();
533
534 assert_eq!(json["title"], "Summer");
535 assert_eq!(json["product_id"], 12345);
536 assert_eq!(json["published_status"], "published");
537
538 let empty_params = CustomCollectionCountParams::default();
539 let empty_json = serde_json::to_value(&empty_params).unwrap();
540 assert_eq!(empty_json, serde_json::json!({}));
541 }
542}