use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AgentCard {
pub name: String,
pub version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub endpoint_url: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub input_modes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub output_modes: Vec<String>,
pub created_at: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_card_serialization_camel_case() {
let card = AgentCard {
name: "test-agent".to_string(),
version: "1.0.0".to_string(),
description: Some("A test agent".to_string()),
tags: vec!["test".to_string()],
endpoint_url: Some("https://example.com".to_string()),
capabilities: vec!["text".to_string()],
input_modes: vec!["text".to_string()],
output_modes: vec!["text".to_string()],
created_at: "2025-01-15T10:30:00Z".to_string(),
updated_at: Some("2025-01-15T11:00:00Z".to_string()),
};
let json = serde_json::to_string(&card).unwrap();
assert!(json.contains("\"endpointUrl\""));
assert!(json.contains("\"createdAt\""));
assert!(json.contains("\"updatedAt\""));
assert!(json.contains("\"inputModes\""));
assert!(json.contains("\"outputModes\""));
assert!(!json.contains("endpoint_url"));
assert!(!json.contains("created_at"));
}
#[test]
fn test_agent_card_round_trip() {
let card = AgentCard {
name: "my-agent".to_string(),
version: "2.0.0".to_string(),
description: None,
tags: vec![],
endpoint_url: None,
capabilities: vec![],
input_modes: vec![],
output_modes: vec![],
created_at: "2025-06-01T00:00:00Z".to_string(),
updated_at: None,
};
let json = serde_json::to_string(&card).unwrap();
let deserialized: AgentCard = serde_json::from_str(&json).unwrap();
assert_eq!(card, deserialized);
}
#[test]
fn test_agent_card_skips_empty_optional_fields() {
let card = AgentCard {
name: "minimal".to_string(),
version: "0.1.0".to_string(),
description: None,
tags: vec![],
endpoint_url: None,
capabilities: vec![],
input_modes: vec![],
output_modes: vec![],
created_at: "2025-01-01T00:00:00Z".to_string(),
updated_at: None,
};
let json = serde_json::to_string(&card).unwrap();
assert!(!json.contains("description"));
assert!(!json.contains("tags"));
assert!(!json.contains("endpointUrl"));
assert!(!json.contains("capabilities"));
assert!(!json.contains("inputModes"));
assert!(!json.contains("outputModes"));
assert!(!json.contains("updatedAt"));
}
#[test]
fn test_agent_card_deserialize_from_camel_case_json() {
let json = r#"{
"name": "from-json",
"version": "1.0.0",
"description": "Deserialized agent",
"tags": ["search", "qa"],
"endpointUrl": "https://example.com/agents/from-json",
"capabilities": ["text", "tool-calling"],
"inputModes": ["text"],
"outputModes": ["text"],
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}"#;
let card: AgentCard = serde_json::from_str(json).unwrap();
assert_eq!(card.name, "from-json");
assert_eq!(card.endpoint_url, Some("https://example.com/agents/from-json".to_string()));
assert_eq!(card.tags, vec!["search", "qa"]);
assert_eq!(card.capabilities, vec!["text", "tool-calling"]);
}
}