1use chrono::{DateTime, NaiveDate, Utc};
52use serde::{Deserialize, Serialize};
53
54use crate::clients::RestClient;
55use crate::rest::{ResourceError, ResourceOperation, ResourcePath, RestResource};
56use crate::HttpMethod;
57
58#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
103pub struct GiftCard {
104 #[serde(skip_serializing)]
107 pub id: Option<u64>,
108
109 #[serde(skip_serializing)]
111 pub balance: Option<String>,
112
113 #[serde(skip_serializing)]
116 pub disabled_at: Option<DateTime<Utc>>,
117
118 #[serde(skip_serializing)]
120 pub line_item_id: Option<u64>,
121
122 #[serde(skip_serializing)]
124 pub api_client_id: Option<u64>,
125
126 #[serde(skip_serializing)]
128 pub user_id: Option<u64>,
129
130 #[serde(skip_serializing)]
133 pub last_characters: Option<String>,
134
135 #[serde(skip_serializing)]
137 pub order_id: Option<u64>,
138
139 #[serde(skip_serializing)]
141 pub created_at: Option<DateTime<Utc>>,
142
143 #[serde(skip_serializing)]
145 pub updated_at: Option<DateTime<Utc>>,
146
147 #[serde(skip_serializing)]
149 pub admin_graphql_api_id: Option<String>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
162 pub code: Option<String>,
163
164 #[serde(skip_serializing_if = "Option::is_none")]
169 pub initial_value: Option<String>,
170
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub currency: Option<String>,
174
175 #[serde(skip_serializing_if = "Option::is_none")]
179 pub customer_id: Option<u64>,
180
181 #[serde(skip_serializing_if = "Option::is_none")]
185 pub note: Option<String>,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
191 pub expires_on: Option<NaiveDate>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
197 pub template_suffix: Option<String>,
198}
199
200impl GiftCard {
201 pub fn is_enabled(&self) -> bool {
205 self.disabled_at.is_none()
206 }
207
208 pub fn is_disabled(&self) -> bool {
212 self.disabled_at.is_some()
213 }
214}
215
216impl RestResource for GiftCard {
217 type Id = u64;
218 type FindParams = GiftCardFindParams;
219 type AllParams = GiftCardListParams;
220 type CountParams = GiftCardCountParams;
221
222 const NAME: &'static str = "GiftCard";
223 const PLURAL: &'static str = "gift_cards";
224
225 const PATHS: &'static [ResourcePath] = &[
230 ResourcePath::new(
231 HttpMethod::Get,
232 ResourceOperation::Find,
233 &["id"],
234 "gift_cards/{id}",
235 ),
236 ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "gift_cards"),
237 ResourcePath::new(
238 HttpMethod::Get,
239 ResourceOperation::Count,
240 &[],
241 "gift_cards/count",
242 ),
243 ResourcePath::new(
244 HttpMethod::Post,
245 ResourceOperation::Create,
246 &[],
247 "gift_cards",
248 ),
249 ResourcePath::new(
250 HttpMethod::Put,
251 ResourceOperation::Update,
252 &["id"],
253 "gift_cards/{id}",
254 ),
255 ];
257
258 fn get_id(&self) -> Option<Self::Id> {
259 self.id
260 }
261}
262
263impl GiftCard {
264 pub async fn disable(&self, client: &RestClient) -> Result<Self, ResourceError> {
291 let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
292 resource: Self::NAME,
293 operation: "disable",
294 })?;
295
296 let path = format!("gift_cards/{id}/disable");
297 let body = serde_json::json!({});
298
299 let response = client.post(&path, body, None).await?;
300
301 if !response.is_ok() {
302 return Err(ResourceError::from_http_response(
303 response.code,
304 &response.body,
305 Self::NAME,
306 Some(&id.to_string()),
307 response.request_id(),
308 ));
309 }
310
311 let gift_card: Self = response
313 .body
314 .get("gift_card")
315 .ok_or_else(|| {
316 ResourceError::Http(crate::clients::HttpError::Response(
317 crate::clients::HttpResponseError {
318 code: response.code,
319 message: "Missing 'gift_card' in response".to_string(),
320 error_reference: response.request_id().map(ToString::to_string),
321 },
322 ))
323 })
324 .and_then(|v| {
325 serde_json::from_value(v.clone()).map_err(|e| {
326 ResourceError::Http(crate::clients::HttpError::Response(
327 crate::clients::HttpResponseError {
328 code: response.code,
329 message: format!("Failed to deserialize gift_card: {e}"),
330 error_reference: response.request_id().map(ToString::to_string),
331 },
332 ))
333 })
334 })?;
335
336 Ok(gift_card)
337 }
338
339 pub async fn search(client: &RestClient, query: &str) -> Result<Vec<Self>, ResourceError> {
362 let path = format!("gift_cards/search");
363
364 let mut query_params = std::collections::HashMap::new();
365 query_params.insert("query".to_string(), query.to_string());
366
367 let response = client.get(&path, Some(query_params)).await?;
368
369 if !response.is_ok() {
370 return Err(ResourceError::from_http_response(
371 response.code,
372 &response.body,
373 Self::NAME,
374 None,
375 response.request_id(),
376 ));
377 }
378
379 let gift_cards: Vec<Self> = response
381 .body
382 .get("gift_cards")
383 .ok_or_else(|| {
384 ResourceError::Http(crate::clients::HttpError::Response(
385 crate::clients::HttpResponseError {
386 code: response.code,
387 message: "Missing 'gift_cards' in response".to_string(),
388 error_reference: response.request_id().map(ToString::to_string),
389 },
390 ))
391 })
392 .and_then(|v| {
393 serde_json::from_value(v.clone()).map_err(|e| {
394 ResourceError::Http(crate::clients::HttpError::Response(
395 crate::clients::HttpResponseError {
396 code: response.code,
397 message: format!("Failed to deserialize gift_cards: {e}"),
398 error_reference: response.request_id().map(ToString::to_string),
399 },
400 ))
401 })
402 })?;
403
404 Ok(gift_cards)
405 }
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
410pub struct GiftCardFindParams {
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub fields: Option<String>,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
420pub struct GiftCardListParams {
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub limit: Option<u32>,
424
425 #[serde(skip_serializing_if = "Option::is_none")]
427 pub since_id: Option<u64>,
428
429 #[serde(skip_serializing_if = "Option::is_none")]
431 pub status: Option<String>,
432
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub fields: Option<String>,
436
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub page_info: Option<String>,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
444pub struct GiftCardCountParams {
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub status: Option<String>,
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::rest::{get_path, ResourceOperation};
454
455 #[test]
456 fn test_gift_card_struct_serialization() {
457 let gift_card = GiftCard {
458 id: Some(123456789),
459 balance: Some("75.00".to_string()),
460 disabled_at: None,
461 line_item_id: Some(111),
462 api_client_id: Some(222),
463 user_id: Some(333),
464 last_characters: Some("abc1".to_string()),
465 order_id: Some(444),
466 created_at: Some(
467 DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z")
468 .unwrap()
469 .with_timezone(&Utc),
470 ),
471 updated_at: Some(
472 DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z")
473 .unwrap()
474 .with_timezone(&Utc),
475 ),
476 admin_graphql_api_id: Some("gid://shopify/GiftCard/123456789".to_string()),
477 code: Some("GIFT1234ABCD5678".to_string()),
478 initial_value: Some("100.00".to_string()),
479 currency: Some("USD".to_string()),
480 customer_id: Some(789012),
481 note: Some("Employee reward".to_string()),
482 expires_on: Some(NaiveDate::from_ymd_opt(2025, 12, 31).unwrap()),
483 template_suffix: Some("premium".to_string()),
484 };
485
486 let json = serde_json::to_string(&gift_card).unwrap();
487 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
488
489 assert_eq!(parsed["code"], "GIFT1234ABCD5678");
491 assert_eq!(parsed["initial_value"], "100.00");
492 assert_eq!(parsed["currency"], "USD");
493 assert_eq!(parsed["customer_id"], 789012);
494 assert_eq!(parsed["note"], "Employee reward");
495 assert_eq!(parsed["expires_on"], "2025-12-31");
496 assert_eq!(parsed["template_suffix"], "premium");
497
498 assert!(parsed.get("id").is_none());
500 assert!(parsed.get("balance").is_none());
501 assert!(parsed.get("disabled_at").is_none());
502 assert!(parsed.get("line_item_id").is_none());
503 assert!(parsed.get("api_client_id").is_none());
504 assert!(parsed.get("user_id").is_none());
505 assert!(parsed.get("last_characters").is_none());
506 assert!(parsed.get("order_id").is_none());
507 assert!(parsed.get("created_at").is_none());
508 assert!(parsed.get("updated_at").is_none());
509 assert!(parsed.get("admin_graphql_api_id").is_none());
510 }
511
512 #[test]
513 fn test_gift_card_deserialization_from_api_response() {
514 let json_str = r#"{
515 "id": 1035197676,
516 "balance": "100.00",
517 "created_at": "2024-01-15T10:30:00Z",
518 "updated_at": "2024-01-15T10:30:00Z",
519 "currency": "USD",
520 "initial_value": "100.00",
521 "disabled_at": null,
522 "line_item_id": 466157049,
523 "api_client_id": 755357713,
524 "user_id": null,
525 "customer_id": 207119551,
526 "note": "Birthday gift for John",
527 "expires_on": "2025-12-31",
528 "template_suffix": null,
529 "last_characters": "0e0e",
530 "order_id": 450789469,
531 "admin_graphql_api_id": "gid://shopify/GiftCard/1035197676"
532 }"#;
533
534 let gift_card: GiftCard = serde_json::from_str(json_str).unwrap();
535
536 assert_eq!(gift_card.id, Some(1035197676));
537 assert_eq!(gift_card.balance.as_deref(), Some("100.00"));
538 assert_eq!(gift_card.currency.as_deref(), Some("USD"));
539 assert_eq!(gift_card.initial_value.as_deref(), Some("100.00"));
540 assert_eq!(gift_card.disabled_at, None);
541 assert_eq!(gift_card.line_item_id, Some(466157049));
542 assert_eq!(gift_card.api_client_id, Some(755357713));
543 assert_eq!(gift_card.user_id, None);
544 assert_eq!(gift_card.customer_id, Some(207119551));
545 assert_eq!(gift_card.note.as_deref(), Some("Birthday gift for John"));
546 assert_eq!(
547 gift_card.expires_on,
548 Some(NaiveDate::from_ymd_opt(2025, 12, 31).unwrap())
549 );
550 assert_eq!(gift_card.template_suffix, None);
551 assert_eq!(gift_card.last_characters.as_deref(), Some("0e0e"));
552 assert_eq!(gift_card.order_id, Some(450789469));
553 assert!(gift_card.created_at.is_some());
554 assert!(gift_card.updated_at.is_some());
555
556 assert_eq!(gift_card.code, None);
558 }
559
560 #[test]
561 fn test_gift_card_path_constants() {
562 let find_path = get_path(GiftCard::PATHS, ResourceOperation::Find, &["id"]);
564 assert!(find_path.is_some());
565 assert_eq!(find_path.unwrap().template, "gift_cards/{id}");
566 assert_eq!(find_path.unwrap().http_method, HttpMethod::Get);
567
568 let all_path = get_path(GiftCard::PATHS, ResourceOperation::All, &[]);
570 assert!(all_path.is_some());
571 assert_eq!(all_path.unwrap().template, "gift_cards");
572
573 let count_path = get_path(GiftCard::PATHS, ResourceOperation::Count, &[]);
575 assert!(count_path.is_some());
576 assert_eq!(count_path.unwrap().template, "gift_cards/count");
577
578 let create_path = get_path(GiftCard::PATHS, ResourceOperation::Create, &[]);
580 assert!(create_path.is_some());
581 assert_eq!(create_path.unwrap().template, "gift_cards");
582 assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
583
584 let update_path = get_path(GiftCard::PATHS, ResourceOperation::Update, &["id"]);
586 assert!(update_path.is_some());
587 assert_eq!(update_path.unwrap().template, "gift_cards/{id}");
588 assert_eq!(update_path.unwrap().http_method, HttpMethod::Put);
589
590 let delete_path = get_path(GiftCard::PATHS, ResourceOperation::Delete, &["id"]);
592 assert!(delete_path.is_none());
593
594 assert_eq!(GiftCard::NAME, "GiftCard");
596 assert_eq!(GiftCard::PLURAL, "gift_cards");
597 }
598
599 #[test]
600 fn test_disable_method_signature() {
601 fn _assert_disable_signature<F, Fut>(f: F)
603 where
604 F: Fn(&GiftCard, &RestClient) -> Fut,
605 Fut: std::future::Future<Output = Result<GiftCard, ResourceError>>,
606 {
607 let _ = f;
608 }
609
610 let gift_card_without_id = GiftCard::default();
612 assert!(gift_card_without_id.get_id().is_none());
613 }
614
615 #[test]
616 fn test_search_method_signature() {
617 fn _assert_search_signature<F, Fut>(f: F)
619 where
620 F: Fn(&RestClient, &str) -> Fut,
621 Fut: std::future::Future<Output = Result<Vec<GiftCard>, ResourceError>>,
622 {
623 let _ = f;
624 }
625 }
626
627 #[test]
628 fn test_gift_card_is_enabled_disabled() {
629 let enabled_gift_card = GiftCard {
630 id: Some(123),
631 balance: Some("100.00".to_string()),
632 disabled_at: None,
633 ..Default::default()
634 };
635
636 assert!(enabled_gift_card.is_enabled());
637 assert!(!enabled_gift_card.is_disabled());
638
639 let disabled_gift_card = GiftCard {
640 id: Some(456),
641 balance: Some("0.00".to_string()),
642 disabled_at: Some(
643 DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z")
644 .unwrap()
645 .with_timezone(&Utc),
646 ),
647 ..Default::default()
648 };
649
650 assert!(!disabled_gift_card.is_enabled());
651 assert!(disabled_gift_card.is_disabled());
652 }
653
654 #[test]
655 fn test_gift_card_get_id_returns_correct_value() {
656 let gift_card_with_id = GiftCard {
657 id: Some(1035197676),
658 balance: Some("100.00".to_string()),
659 ..Default::default()
660 };
661 assert_eq!(gift_card_with_id.get_id(), Some(1035197676));
662
663 let gift_card_without_id = GiftCard {
664 id: None,
665 initial_value: Some("50.00".to_string()),
666 ..Default::default()
667 };
668 assert_eq!(gift_card_without_id.get_id(), None);
669 }
670
671 #[test]
672 fn test_gift_card_list_params_serialization() {
673 let params = GiftCardListParams {
674 limit: Some(50),
675 since_id: Some(12345),
676 status: Some("enabled".to_string()),
677 fields: Some("id,balance,last_characters".to_string()),
678 page_info: None,
679 };
680
681 let json = serde_json::to_value(¶ms).unwrap();
682
683 assert_eq!(json["limit"], 50);
684 assert_eq!(json["since_id"], 12345);
685 assert_eq!(json["status"], "enabled");
686 assert_eq!(json["fields"], "id,balance,last_characters");
687 assert!(json.get("page_info").is_none());
688
689 let empty_params = GiftCardListParams::default();
691 let empty_json = serde_json::to_value(&empty_params).unwrap();
692 assert_eq!(empty_json, serde_json::json!({}));
693 }
694
695 #[test]
696 fn test_gift_card_count_params_serialization() {
697 let params = GiftCardCountParams {
698 status: Some("disabled".to_string()),
699 };
700
701 let json = serde_json::to_value(¶ms).unwrap();
702 assert_eq!(json["status"], "disabled");
703
704 let empty_params = GiftCardCountParams::default();
706 let empty_json = serde_json::to_value(&empty_params).unwrap();
707 assert_eq!(empty_json, serde_json::json!({}));
708 }
709
710 #[test]
711 fn test_gift_card_updatable_fields() {
712 let update_gift_card = GiftCard {
714 expires_on: Some(NaiveDate::from_ymd_opt(2026, 6, 30).unwrap()),
715 note: Some("Updated note".to_string()),
716 template_suffix: Some("custom".to_string()),
717 customer_id: Some(999999),
718 ..Default::default()
719 };
720
721 let json = serde_json::to_value(&update_gift_card).unwrap();
722
723 assert_eq!(json["expires_on"], "2026-06-30");
725 assert_eq!(json["note"], "Updated note");
726 assert_eq!(json["template_suffix"], "custom");
727 assert_eq!(json["customer_id"], 999999);
728 }
729
730 #[test]
731 fn test_gift_card_code_is_write_only() {
732 let create_gift_card = GiftCard {
734 initial_value: Some("100.00".to_string()),
735 code: Some("MYGIFTCODE1234".to_string()),
736 ..Default::default()
737 };
738
739 let json = serde_json::to_value(&create_gift_card).unwrap();
740 assert_eq!(json["code"], "MYGIFTCODE1234");
741 assert_eq!(json["initial_value"], "100.00");
742
743 let api_response = r#"{
745 "id": 123,
746 "balance": "100.00",
747 "initial_value": "100.00",
748 "last_characters": "1234"
749 }"#;
750
751 let gift_card: GiftCard = serde_json::from_str(api_response).unwrap();
752 assert_eq!(gift_card.code, None);
753 assert_eq!(gift_card.last_characters.as_deref(), Some("1234"));
754 }
755}