use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::{TagId, UserId};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(
clippy::struct_excessive_bools,
reason = "matches ZenMoney API schema which has multiple boolean flags"
)]
pub struct Tag {
pub id: TagId,
#[serde(with = "chrono::serde::ts_seconds")]
pub changed: DateTime<Utc>,
pub user: UserId,
pub title: String,
pub parent: Option<TagId>,
pub icon: Option<String>,
pub picture: Option<String>,
pub color: Option<i64>,
pub show_income: bool,
pub show_outcome: bool,
pub budget_income: bool,
pub budget_outcome: bool,
pub required: Option<bool>,
#[serde(default)]
pub static_id: Option<String>,
#[serde(default)]
pub archive: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_tag() {
let json = r#"{
"id": "tag-001",
"changed": 1700000000,
"user": 123,
"title": "Groceries",
"parent": null,
"icon": "food",
"picture": null,
"color": -16711936,
"showIncome": false,
"showOutcome": true,
"budgetIncome": false,
"budgetOutcome": true,
"required": null
}"#;
let tag: Tag = serde_json::from_str(json).unwrap();
assert_eq!(tag.id, TagId::new("tag-001".to_owned()));
assert_eq!(tag.title, "Groceries");
assert!(tag.parent.is_none());
assert!(tag.show_outcome);
assert!(tag.required.is_none());
}
#[test]
fn deserialize_tag_with_parent() {
let json = r#"{
"id": "tag-002",
"changed": 1700000000,
"user": 123,
"title": "Fast Food",
"parent": "tag-001",
"icon": null,
"picture": null,
"color": null,
"showIncome": false,
"showOutcome": true,
"budgetIncome": false,
"budgetOutcome": true,
"required": true
}"#;
let tag: Tag = serde_json::from_str(json).unwrap();
assert_eq!(tag.parent, Some(TagId::new("tag-001".to_owned())));
assert_eq!(tag.required, Some(true));
}
#[test]
fn serialize_roundtrip() {
let tag = Tag {
id: TagId::new("t-1".to_owned()),
changed: DateTime::from_timestamp(1_700_000_000, 0).unwrap(),
user: UserId::new(1),
title: "Test".to_owned(),
parent: None,
icon: None,
picture: None,
color: None,
show_income: true,
show_outcome: true,
budget_income: false,
budget_outcome: false,
required: Some(false),
static_id: None,
archive: None,
};
let json = serde_json::to_string(&tag).unwrap();
let deserialized: Tag = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, tag);
}
}