Skip to main content

a2a_client/utils/
formatters.rs

1//! Formatting utilities for displaying A2A types
2
3use a2a_rs::domain::{Part, TaskState};
4
5/// Format a task state for display
6pub fn format_task_state(state: &TaskState) -> String {
7    match *state {
8        TaskState::Submitted => "Submitted",
9        TaskState::Working => "Working",
10        TaskState::InputRequired => "Input Required",
11        TaskState::Completed => "Completed",
12        TaskState::Canceled => "Canceled",
13        TaskState::Failed => "Failed",
14        TaskState::Rejected => "Rejected",
15        TaskState::AuthRequired => "Auth Required",
16        TaskState::Unknown => "Unknown",
17    }
18    .to_string()
19}
20
21/// Extract and format text content from message parts
22pub fn format_message_content(parts: &[Part]) -> String {
23    use a2a_rs::domain::part::Content;
24    parts
25        .iter()
26        .map(|part| match &part.content {
27            Some(Content::Text(text)) => text.clone(),
28            Some(Content::Raw(_)) => format!(
29                "[File: {}]",
30                if part.filename.is_empty() {
31                    "unnamed"
32                } else {
33                    &part.filename
34                }
35            ),
36            Some(Content::Url(url)) => format!(
37                "[File URI: {} ({})]",
38                if part.filename.is_empty() {
39                    "unnamed"
40                } else {
41                    &part.filename
42                },
43                url
44            ),
45            Some(Content::Data(data)) => {
46                let name = serde_json::to_value(&**data)
47                    .ok()
48                    .and_then(|v| v.get("name").cloned())
49                    .map(|v| {
50                        if let serde_json::Value::String(s) = v {
51                            s
52                        } else {
53                            v.to_string()
54                        }
55                    })
56                    .unwrap_or_else(|| "unnamed".to_string());
57                format!("[Data: {}]", name)
58            }
59            None => "[Empty Part]".to_string(),
60        })
61        .collect::<Vec<_>>()
62        .join("\n")
63}
64
65/// Truncate text for preview display
66pub fn truncate_preview(text: &str, max_len: usize) -> String {
67    if text.len() <= max_len {
68        text.to_string()
69    } else {
70        format!("{}...", text.chars().take(max_len).collect::<String>())
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use a2a_rs::domain::Part;
78
79    #[test]
80    fn test_format_task_state() {
81        assert_eq!(format_task_state(&TaskState::Submitted), "Submitted");
82        assert_eq!(format_task_state(&TaskState::Working), "Working");
83        assert_eq!(
84            format_task_state(&TaskState::InputRequired),
85            "Input Required"
86        );
87        assert_eq!(format_task_state(&TaskState::Completed), "Completed");
88        assert_eq!(format_task_state(&TaskState::Canceled), "Canceled");
89        assert_eq!(format_task_state(&TaskState::Failed), "Failed");
90        assert_eq!(format_task_state(&TaskState::Rejected), "Rejected");
91        assert_eq!(format_task_state(&TaskState::AuthRequired), "Auth Required");
92        assert_eq!(format_task_state(&TaskState::Unknown), "Unknown");
93    }
94
95    #[test]
96    fn test_format_message_content_text() {
97        let parts = vec![Part::text("Hello".to_string())];
98        assert_eq!(format_message_content(&parts), "Hello");
99    }
100
101    #[test]
102    fn test_format_message_content_multiple() {
103        let parts = vec![
104            Part::text("Here is file".to_string()),
105            Part::file_from_bytes(vec![], Some("test.txt".to_string()), None),
106            Part::data({
107                let mut map = serde_json::Map::new();
108                map.insert(
109                    "name".to_string(),
110                    serde_json::Value::String("metadata".to_string()),
111                );
112                serde_json::from_value(serde_json::Value::Object(map)).unwrap()
113            }),
114        ];
115
116        let expected = "Here is file\n[File: test.txt]\n[Data: metadata]";
117        assert_eq!(format_message_content(&parts), expected);
118    }
119
120    #[test]
121    fn test_truncate_preview() {
122        assert_eq!(truncate_preview("Short", 10), "Short");
123        assert_eq!(truncate_preview("ExactLength", 11), "ExactLength");
124        assert_eq!(truncate_preview("This is too long", 10), "This is to...");
125
126        // Multi-byte character test
127        assert_eq!(truncate_preview("你好世界", 2), "你好...");
128    }
129}