1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct A2aMessage {
9 pub id: Uuid,
10 pub sender: String,
11 pub recipient: String,
12 pub message_type: A2aMessageType,
13 pub timestamp: DateTime<Utc>,
14 pub payload: serde_json::Value,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum A2aMessageType {
21 Request,
22 Response,
23 Notification,
24 Error,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum AwpMessageType {
34 VisitorIntentSignal,
36 ContentGapSignal,
38 PaymentIntent,
40 SupportEscalation,
42 ReviewSignal,
44 OperationsProposal,
46 InvokeCapability,
48 RenderUi,
50 OutboundTrigger,
52}
53
54impl std::fmt::Display for AwpMessageType {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 Self::VisitorIntentSignal => write!(f, "visitor_intent_signal"),
58 Self::ContentGapSignal => write!(f, "content_gap_signal"),
59 Self::PaymentIntent => write!(f, "payment_intent"),
60 Self::SupportEscalation => write!(f, "support_escalation"),
61 Self::ReviewSignal => write!(f, "review_signal"),
62 Self::OperationsProposal => write!(f, "operations_proposal"),
63 Self::InvokeCapability => write!(f, "invoke_capability"),
64 Self::RenderUi => write!(f, "render_ui"),
65 Self::OutboundTrigger => write!(f, "outbound_trigger"),
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct AwpTypedMessage {
77 pub id: Uuid,
78 pub sender: String,
79 pub recipient: String,
80 pub awp_type: AwpMessageType,
81 pub timestamp: DateTime<Utc>,
82 pub payload: serde_json::Value,
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_a2a_message_serde_round_trip() {
91 let msg = A2aMessage {
92 id: Uuid::now_v7(),
93 sender: "agent-a".to_string(),
94 recipient: "agent-b".to_string(),
95 message_type: A2aMessageType::Request,
96 timestamp: Utc::now(),
97 payload: serde_json::json!({"action": "greet"}),
98 };
99 let json = serde_json::to_string(&msg).unwrap();
100 let deserialized: A2aMessage = serde_json::from_str(&json).unwrap();
101 assert_eq!(msg, deserialized);
102 }
103
104 #[test]
105 fn test_message_type_serde() {
106 for mt in [
107 A2aMessageType::Request,
108 A2aMessageType::Response,
109 A2aMessageType::Notification,
110 A2aMessageType::Error,
111 ] {
112 let json = serde_json::to_string(&mt).unwrap();
113 let deserialized: A2aMessageType = serde_json::from_str(&json).unwrap();
114 assert_eq!(mt, deserialized);
115 }
116 }
117
118 #[test]
119 fn test_message_type_lowercase() {
120 assert_eq!(serde_json::to_string(&A2aMessageType::Request).unwrap(), "\"request\"");
121 assert_eq!(serde_json::to_string(&A2aMessageType::Response).unwrap(), "\"response\"");
122 assert_eq!(
123 serde_json::to_string(&A2aMessageType::Notification).unwrap(),
124 "\"notification\""
125 );
126 assert_eq!(serde_json::to_string(&A2aMessageType::Error).unwrap(), "\"error\"");
127 }
128
129 #[test]
130 fn test_awp_message_type_serde_round_trip() {
131 let types = [
132 AwpMessageType::VisitorIntentSignal,
133 AwpMessageType::ContentGapSignal,
134 AwpMessageType::PaymentIntent,
135 AwpMessageType::SupportEscalation,
136 AwpMessageType::ReviewSignal,
137 AwpMessageType::OperationsProposal,
138 AwpMessageType::InvokeCapability,
139 AwpMessageType::RenderUi,
140 AwpMessageType::OutboundTrigger,
141 ];
142 for mt in types {
143 let json = serde_json::to_string(&mt).unwrap();
144 let deserialized: AwpMessageType = serde_json::from_str(&json).unwrap();
145 assert_eq!(mt, deserialized);
146 }
147 }
148
149 #[test]
150 fn test_awp_message_type_snake_case() {
151 assert_eq!(
152 serde_json::to_string(&AwpMessageType::VisitorIntentSignal).unwrap(),
153 "\"visitor_intent_signal\""
154 );
155 assert_eq!(
156 serde_json::to_string(&AwpMessageType::PaymentIntent).unwrap(),
157 "\"payment_intent\""
158 );
159 assert_eq!(serde_json::to_string(&AwpMessageType::RenderUi).unwrap(), "\"render_ui\"");
160 }
161
162 #[test]
163 fn test_awp_message_type_display() {
164 assert_eq!(AwpMessageType::VisitorIntentSignal.to_string(), "visitor_intent_signal");
165 assert_eq!(AwpMessageType::SupportEscalation.to_string(), "support_escalation");
166 assert_eq!(AwpMessageType::OutboundTrigger.to_string(), "outbound_trigger");
167 }
168
169 #[test]
170 fn test_awp_typed_message_serde_round_trip() {
171 let msg = AwpTypedMessage {
172 id: Uuid::now_v7(),
173 sender: "visitor-agent".to_string(),
174 recipient: "payment-agent".to_string(),
175 awp_type: AwpMessageType::PaymentIntent,
176 timestamp: Utc::now(),
177 payload: serde_json::json!({"sku": "WIDGET-001", "amount": 2500}),
178 };
179 let json = serde_json::to_string(&msg).unwrap();
180 let deserialized: AwpTypedMessage = serde_json::from_str(&json).unwrap();
181 assert_eq!(msg, deserialized);
182 }
183}