xray_lite/
namespace.rs

1//! Namespace encapsulation for subsegments.
2
3use crate::segment::{AwsOperation, Http, Request, Response, Subsegment};
4
5/// Namespace.
6pub trait Namespace {
7    /// Name of the namespace.
8    ///
9    /// `prefix` may be ignored.
10    fn name(&self, prefix: &str) -> String;
11
12    /// Updates the subsegment.
13    fn update_subsegment(&self, subsegment: &mut Subsegment);
14}
15
16/// Namespace for an AWS service.
17#[derive(Debug)]
18pub struct AwsNamespace {
19    service: String,
20    operation: String,
21    request_id: Option<String>,
22    response_status: Option<u16>,
23}
24
25impl AwsNamespace {
26    /// Creates a namespace for an AWS service operation.
27    pub fn new(service: impl Into<String>, operation: impl Into<String>) -> Self {
28        Self {
29            service: service.into(),
30            operation: operation.into(),
31            request_id: None,
32            response_status: None,
33        }
34    }
35
36    /// Sets the request ID.
37    pub fn request_id(&mut self, request_id: impl Into<String>) -> &mut Self {
38        self.request_id = Some(request_id.into());
39        self
40    }
41
42    /// Sets the response status.
43    pub fn response_status(&mut self, status: u16) -> &mut Self {
44        self.response_status = Some(status);
45        self
46    }
47}
48
49impl Namespace for AwsNamespace {
50    fn name(&self, _prefix: &str) -> String {
51        self.service.clone()
52    }
53
54    fn update_subsegment(&self, subsegment: &mut Subsegment) {
55        if subsegment.namespace.is_none() {
56            subsegment.namespace = Some("aws".to_string());
57        }
58        if let Some(aws) = subsegment.aws.as_mut() {
59            if aws.operation.is_none() {
60                aws.operation = Some(self.operation.clone());
61            }
62            if aws.request_id.is_none() {
63                aws.request_id = self.request_id.clone();
64            }
65        } else {
66            subsegment.aws = Some(AwsOperation {
67                operation: Some(self.operation.clone()),
68                request_id: self.request_id.clone(),
69                ..AwsOperation::default()
70            });
71        }
72        if let Some(response_status) = self.response_status {
73            if let Some(http) = subsegment.http.as_mut() {
74                if let Some(response) = http.response.as_mut() {
75                    if response.status.is_none() {
76                        response.status = Some(response_status);
77                    }
78                } else {
79                    http.response = Some(Response {
80                        status: Some(response_status),
81                        ..Response::default()
82                    });
83                }
84            } else {
85                subsegment.http = Some(Http {
86                    response: Some(Response {
87                        status: Some(response_status),
88                        ..Response::default()
89                    }),
90                    ..Http::default()
91                });
92            }
93        }
94    }
95}
96
97/// Namespace for an arbitrary remote service.
98#[derive(Debug)]
99pub struct RemoteNamespace {
100    name: String,
101    method: String,
102    url: String,
103    response_status: Option<u16>,
104}
105
106impl RemoteNamespace {
107    /// Creates a namespace for a remote service.
108    pub fn new(name: impl Into<String>, method: impl Into<String>, url: impl Into<String>) -> Self {
109        Self {
110            name: name.into(),
111            method: method.into(),
112            url: url.into(),
113            response_status: None,
114        }
115    }
116
117    /// Sets the response status.
118    pub fn response_status(&mut self, status: u16) -> &mut Self {
119        self.response_status = Some(status);
120        self
121    }
122}
123
124impl Namespace for RemoteNamespace {
125    fn name(&self, _prefix: &str) -> String {
126        self.name.clone()
127    }
128
129    fn update_subsegment(&self, subsegment: &mut Subsegment) {
130        if subsegment.namespace.is_none() {
131            subsegment.namespace = Some("remote".to_string());
132        }
133        if let Some(http) = subsegment.http.as_mut() {
134            if let Some(request) = http.request.as_mut() {
135                if request.method.is_none() {
136                    request.method = Some(self.method.clone());
137                }
138                if request.url.is_none() {
139                    request.url = Some(self.url.clone());
140                }
141            } else {
142                http.request = Some(Request {
143                    url: Some(self.url.clone()),
144                    method: Some(self.method.clone()),
145                    ..Request::default()
146                });
147            }
148        } else {
149            subsegment.http = Some(Http {
150                request: Some(Request {
151                    url: Some(self.url.clone()),
152                    method: Some(self.method.clone()),
153                    ..Request::default()
154                }),
155                ..Http::default()
156            });
157        }
158        if let Some(response_status) = self.response_status {
159            let http = subsegment.http.as_mut().expect("http must have been set");
160            if let Some(response) = http.response.as_mut() {
161                if response.status.is_none() {
162                    response.status = Some(response_status);
163                }
164            } else {
165                http.response = Some(Response {
166                    status: Some(response_status),
167                    ..Response::default()
168                });
169            }
170        }
171    }
172}
173
174/// Namespace for a custom subsegment.
175#[derive(Debug)]
176pub struct CustomNamespace {
177    name: String,
178}
179
180impl CustomNamespace {
181    /// Creates a namespace for a custom subsegment.
182    pub fn new(name: impl Into<String>) -> Self {
183        Self { name: name.into() }
184    }
185}
186
187impl Namespace for CustomNamespace {
188    fn name(&self, prefix: &str) -> String {
189        format!("{}{}", prefix, self.name)
190    }
191
192    // does nothing
193    fn update_subsegment(&self, _subsegment: &mut Subsegment) {}
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn aws_namespace_should_have_service_name_as_name() {
202        let namespace = AwsNamespace::new("S3", "GetObject");
203        assert_eq!(namespace.name(""), "S3");
204        assert_eq!(namespace.name("prefix"), "S3");
205    }
206
207    #[test]
208    fn aws_namespace_should_update_subsegment_with_aws_operation() {
209        let namespace = AwsNamespace::new("S3", "GetObject");
210        let mut subsegment = Subsegment::default();
211        namespace.update_subsegment(&mut subsegment);
212        assert_eq!(subsegment.namespace.unwrap(), "aws");
213        assert_eq!(subsegment.aws.unwrap().operation.unwrap(), "GetObject");
214    }
215
216    #[test]
217    fn aws_namespace_should_update_subsegment_with_request_id() {
218        let mut namespace = AwsNamespace::new("S3", "GetObject");
219        namespace.request_id("12345");
220        let mut subsegment = Subsegment::default();
221        namespace.update_subsegment(&mut subsegment);
222        assert_eq!(subsegment.aws.unwrap().request_id.unwrap(), "12345");
223    }
224
225    #[test]
226    fn aws_namespace_should_update_subsegment_with_response_status() {
227        let mut namespace = AwsNamespace::new("S3", "GetObject");
228        namespace.response_status(200);
229        let mut subsegment = Subsegment::default();
230        namespace.update_subsegment(&mut subsegment);
231        assert_eq!(
232            subsegment
233                .http
234                .expect("http")
235                .response
236                .expect("response")
237                .status
238                .expect("status"),
239            200,
240        );
241    }
242
243    #[test]
244    fn remote_namespace_should_have_name_as_name() {
245        let namespace = RemoteNamespace::new("codemonger.io", "GET", "https://codemonger.io/");
246        assert_eq!(namespace.name(""), "codemonger.io");
247        assert_eq!(namespace.name("prefix"), "codemonger.io");
248    }
249
250    #[test]
251    fn remote_namespace_should_update_subsegment_with_remote_service() {
252        let namespace = RemoteNamespace::new("codemonger.io", "GET", "https://codemonger.io/");
253        let mut subsegment = Subsegment::default();
254        namespace.update_subsegment(&mut subsegment);
255        assert_eq!(subsegment.namespace.unwrap(), "remote");
256        let request = subsegment.http.expect("http").request.expect("request");
257        assert_eq!(request.method.unwrap(), "GET");
258        assert_eq!(request.url.unwrap(), "https://codemonger.io/");
259    }
260
261    #[test]
262    fn remote_namespace_should_update_subsegment_with_response_status() {
263        let mut namespace = RemoteNamespace::new("codemonger.io", "GET", "https://codemonger.io/");
264        namespace.response_status(200);
265        let mut subsegment = Subsegment::default();
266        namespace.update_subsegment(&mut subsegment);
267        assert_eq!(
268            subsegment
269                .http
270                .expect("http")
271                .response
272                .expect("response")
273                .status
274                .expect("status"),
275            200,
276        );
277    }
278
279    #[test]
280    fn custom_namespace_should_have_prefixed_name() {
281        let namespace = CustomNamespace::new("TestSubsegment");
282        assert_eq!(namespace.name(""), "TestSubsegment");
283        assert_eq!(namespace.name("prefix"), "prefixTestSubsegment");
284    }
285}