a2a_protocol_server/
call_context.rs1use std::collections::HashMap;
31
32#[derive(Debug, Clone)]
37pub struct CallContext {
38 method: String,
40
41 caller_identity: Option<String>,
43
44 extensions: Vec<String>,
46
47 request_id: Option<String>,
49
50 http_headers: HashMap<String, String>,
54}
55
56impl CallContext {
57 #[must_use]
59 pub fn method(&self) -> &str {
60 &self.method
61 }
62
63 #[must_use]
65 pub fn caller_identity(&self) -> Option<&str> {
66 self.caller_identity.as_deref()
67 }
68
69 #[must_use]
71 pub fn extensions(&self) -> &[String] {
72 &self.extensions
73 }
74
75 #[must_use]
77 pub fn request_id(&self) -> Option<&str> {
78 self.request_id.as_deref()
79 }
80
81 #[must_use]
83 pub const fn http_headers(&self) -> &HashMap<String, String> {
84 &self.http_headers
85 }
86}
87
88impl CallContext {
89 #[must_use]
91 pub fn new(method: impl Into<String>) -> Self {
92 Self {
93 method: method.into(),
94 caller_identity: None,
95 extensions: Vec::new(),
96 request_id: None,
97 http_headers: HashMap::new(),
98 }
99 }
100
101 #[must_use]
103 pub fn with_caller_identity(mut self, identity: String) -> Self {
104 self.caller_identity = Some(identity);
105 self
106 }
107
108 #[must_use]
110 pub fn with_extensions(mut self, extensions: Vec<String>) -> Self {
111 self.extensions = extensions;
112 self
113 }
114
115 #[must_use]
117 pub fn with_request_id(mut self, id: impl Into<String>) -> Self {
118 self.request_id = Some(id.into());
119 self
120 }
121
122 #[must_use]
127 pub fn with_http_headers(mut self, headers: HashMap<String, String>) -> Self {
128 if let Some(rid) = headers.get("x-request-id") {
129 self.request_id = Some(rid.clone());
130 }
131 self.http_headers = headers;
132 self
133 }
134
135 #[must_use]
139 pub fn with_http_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
140 let key = key.into().to_ascii_lowercase();
141 let value = value.into();
142 if key == "x-request-id" {
143 self.request_id = Some(value.clone());
144 }
145 self.http_headers.insert(key, value);
146 self
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn with_http_header_x_request_id_populates_request_id() {
156 let ctx = CallContext::new("test").with_http_header("x-request-id", "req-42");
157 assert_eq!(ctx.request_id(), Some("req-42"));
158 assert_eq!(
159 ctx.http_headers().get("x-request-id").map(String::as_str),
160 Some("req-42")
161 );
162 }
163
164 #[test]
165 fn with_http_header_other_key_does_not_populate_request_id() {
166 let ctx = CallContext::new("test").with_http_header("authorization", "Bearer tok");
167 assert!(ctx.request_id().is_none());
168 assert_eq!(
169 ctx.http_headers().get("authorization").map(String::as_str),
170 Some("Bearer tok")
171 );
172 }
173
174 #[test]
175 fn with_request_id_sets_field() {
176 let ctx = CallContext::new("test").with_request_id("req-99");
177 assert_eq!(ctx.request_id(), Some("req-99"));
178 }
179
180 #[test]
181 fn with_http_headers_extracts_request_id() {
182 let mut headers = HashMap::new();
183 headers.insert("x-request-id".to_owned(), "trace-123".to_owned());
184 headers.insert("content-type".to_owned(), "application/json".to_owned());
185
186 let ctx = CallContext::new("test").with_http_headers(headers);
187 assert_eq!(ctx.request_id(), Some("trace-123"));
188 assert_eq!(
189 ctx.http_headers().get("content-type").map(String::as_str),
190 Some("application/json")
191 );
192 }
193
194 #[test]
195 fn with_http_headers_without_request_id() {
196 let mut headers = HashMap::new();
197 headers.insert("authorization".to_owned(), "Bearer tok".to_owned());
198
199 let ctx = CallContext::new("test").with_http_headers(headers);
200 assert!(ctx.request_id().is_none());
201 }
202
203 #[test]
204 fn with_caller_identity_sets_field() {
205 let ctx = CallContext::new("test").with_caller_identity("user@example.com".into());
206 assert_eq!(ctx.caller_identity(), Some("user@example.com"));
207 }
208
209 #[test]
210 fn with_extensions_sets_field() {
211 let ctx = CallContext::new("test").with_extensions(vec!["ext1".into(), "ext2".into()]);
212 assert_eq!(ctx.extensions(), &["ext1", "ext2"]);
213 }
214
215 #[test]
216 fn new_defaults_are_empty() {
217 let ctx = CallContext::new("method");
218 assert_eq!(ctx.method(), "method");
219 assert!(ctx.caller_identity().is_none());
220 assert!(ctx.extensions().is_empty());
221 assert!(ctx.request_id().is_none());
222 assert!(ctx.http_headers().is_empty());
223 }
224}