From 0707628d58dc9d54b296d09f5d477306d85aeb26 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 19 Apr 2026 13:05:36 -0400 Subject: [PATCH] feat(core): flesh out CardCore + CardKind Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/idfoto-core/src/item_types/card.rs | 55 ++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/idfoto-core/src/item_types/card.rs b/crates/idfoto-core/src/item_types/card.rs index 481eae3..55247d6 100644 --- a/crates/idfoto-core/src/item_types/card.rs +++ b/crates/idfoto-core/src/item_types/card.rs @@ -1,7 +1,25 @@ +//! Card: number, holder, expiry (MonthYear), CVV, PIN, kind. + use serde::{Deserialize, Serialize}; +use zeroize::Zeroizing; + +use crate::time::MonthYear; #[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct CardCore {} +pub struct CardCore { + #[serde(skip_serializing_if = "Option::is_none")] + pub number: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub holder: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub expiry: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cvv: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub pin: Option>, + #[serde(default)] + pub kind: CardKind, +} #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] @@ -13,3 +31,38 @@ pub enum CardKind { Loyalty, Other, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn card_full_round_trip() { + let card = CardCore { + number: Some(Zeroizing::new("4111111111111111".into())), + holder: Some("Alice Doe".into()), + expiry: Some(MonthYear::new(12, 2030).unwrap()), + cvv: Some(Zeroizing::new("123".into())), + pin: Some(Zeroizing::new("0000".into())), + kind: CardKind::Credit, + }; + let json = serde_json::to_string(&card).unwrap(); + let parsed: CardCore = serde_json::from_str(&json).unwrap(); + assert_eq!(parsed.holder.as_deref(), Some("Alice Doe")); + assert_eq!(parsed.kind, CardKind::Credit); + assert_eq!(parsed.expiry, Some(MonthYear::new(12, 2030).unwrap())); + } + + #[test] + fn card_kind_default_is_credit() { + let json = "{}"; + let card: CardCore = serde_json::from_str(json).unwrap(); + assert_eq!(card.kind, CardKind::Credit); + } + + #[test] + fn card_kind_serializes_snake_case() { + let json = serde_json::to_string(&CardKind::Loyalty).unwrap(); + assert_eq!(json, "\"loyalty\""); + } +}