use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use crate::validation::priority::migrate_string_priority;
const DEFAULT_PRIORITY_LEVELS: u32 = 3;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommonMetadata {
#[serde(default)]
pub display_number: u32,
pub status: String,
#[serde(deserialize_with = "deserialize_priority")]
pub priority: u32,
pub created_at: String,
pub updated_at: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub custom_fields: HashMap<String, serde_json::Value>,
}
impl CommonMetadata {
#[must_use]
pub fn new(
display_number: u32,
status: String,
priority: u32,
custom_fields: HashMap<String, serde_json::Value>,
) -> Self {
let now = crate::util::now_iso();
Self {
display_number,
status,
priority,
created_at: now.clone(),
updated_at: now,
custom_fields,
}
}
}
pub fn deserialize_priority<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::{self, Visitor};
struct PriorityVisitor;
impl Visitor<'_> for PriorityVisitor {
type Value = u32;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a priority number or string")
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value as u32)
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
if value < 0 {
Err(E::custom("priority cannot be negative"))
} else {
Ok(value as u32)
}
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(migrate_string_priority(value, DEFAULT_PRIORITY_LEVELS))
}
}
deserializer.deserialize_any(PriorityVisitor)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_common_metadata_new() {
let metadata = CommonMetadata::new(1, "open".to_string(), 1, HashMap::new());
assert_eq!(metadata.display_number, 1);
assert_eq!(metadata.status, "open");
assert_eq!(metadata.priority, 1);
assert!(!metadata.created_at.is_empty());
assert!(!metadata.updated_at.is_empty());
}
#[test]
fn test_deserialize_priority_number() {
let json =
r#"{"status":"open","priority":1,"createdAt":"2024-01-01","updatedAt":"2024-01-01"}"#;
let metadata: CommonMetadata = serde_json::from_str(json).unwrap();
assert_eq!(metadata.priority, 1);
}
#[test]
fn test_deserialize_priority_string_high() {
let json = r#"{"status":"open","priority":"high","createdAt":"2024-01-01","updatedAt":"2024-01-01"}"#;
let metadata: CommonMetadata = serde_json::from_str(json).unwrap();
assert_eq!(metadata.priority, 1);
}
#[test]
fn test_deserialize_priority_string_medium() {
let json = r#"{"status":"open","priority":"medium","createdAt":"2024-01-01","updatedAt":"2024-01-01"}"#;
let metadata: CommonMetadata = serde_json::from_str(json).unwrap();
assert_eq!(metadata.priority, 2);
}
#[test]
fn test_deserialize_priority_string_low() {
let json = r#"{"status":"open","priority":"low","createdAt":"2024-01-01","updatedAt":"2024-01-01"}"#;
let metadata: CommonMetadata = serde_json::from_str(json).unwrap();
assert_eq!(metadata.priority, 3);
}
#[test]
fn test_serialize_priority_as_number() {
let metadata = CommonMetadata::new(1, "open".to_string(), 2, HashMap::new());
let json = serde_json::to_string(&metadata).unwrap();
assert!(json.contains(r#""priority":2"#));
}
#[test]
fn test_deserialize_legacy_without_display_number() {
let json =
r#"{"status":"open","priority":1,"createdAt":"2024-01-01","updatedAt":"2024-01-01"}"#;
let metadata: CommonMetadata = serde_json::from_str(json).unwrap();
assert_eq!(metadata.display_number, 0);
}
#[test]
fn test_serialize_display_number() {
let metadata = CommonMetadata::new(42, "open".to_string(), 1, HashMap::new());
let json = serde_json::to_string(&metadata).unwrap();
assert!(json.contains(r#""displayNumber":42"#));
}
}