1use crate::{SegmentId, TraceId};
5use std::{
6 collections::HashMap,
7 fmt::{self, Display},
8 str::FromStr,
9};
10
11#[derive(PartialEq, Clone, Copy, Debug, Default)]
12pub enum SamplingDecision {
13 Sampled,
16 NotSampled,
19 Requested,
23 #[default]
25 Unknown,
26}
27
28impl<'a> From<&'a str> for SamplingDecision {
29 fn from(value: &'a str) -> Self {
30 match value {
31 "Sampled=1" => SamplingDecision::Sampled,
32 "Sampled=0" => SamplingDecision::NotSampled,
33 "Sampled=?" => SamplingDecision::Requested,
34 _ => SamplingDecision::Unknown,
35 }
36 }
37}
38
39impl Display for SamplingDecision {
40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41 write!(
42 f,
43 "{}",
44 match self {
45 SamplingDecision::Sampled => "Sampled=1",
46 SamplingDecision::NotSampled => "Sampled=0",
47 SamplingDecision::Requested => "Sampled=?",
48 _ => "",
49 }
50 )?;
51 Ok(())
52 }
53}
54
55#[derive(PartialEq, Clone, Debug, Default)]
57pub struct Header {
58 pub(crate) trace_id: TraceId,
59 pub(crate) parent_id: Option<SegmentId>,
60 pub(crate) sampling_decision: SamplingDecision,
61 additional_data: HashMap<String, String>,
62}
63
64impl Header {
65 pub const NAME: &'static str = "X-Amzn-Trace-Id";
69
70 pub fn new(trace_id: TraceId) -> Self {
72 Header {
73 trace_id,
74 ..Header::default()
75 }
76 }
77
78 pub fn with_parent_id(&self, parent_id: SegmentId) -> Self {
80 Self {
81 trace_id: self.trace_id.clone(),
82 parent_id: Some(parent_id),
83 sampling_decision: self.sampling_decision,
84 additional_data: self.additional_data.clone(),
85 }
86 }
87
88 pub fn with_sampling_decision(&self, decision: SamplingDecision) -> Self {
90 Self {
91 trace_id: self.trace_id.clone(),
92 parent_id: self.parent_id.clone(),
93 sampling_decision: decision,
94 additional_data: self.additional_data.clone(),
95 }
96 }
97
98 pub fn insert_data(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
100 self.additional_data.insert(key.into(), value.into());
101 self
102 }
103}
104
105impl FromStr for Header {
106 type Err = String;
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 s.split(';')
109 .try_fold(Header::default(), |mut header, line| {
110 if let Some(trace_id) = line.strip_prefix("Root=") {
111 header.trace_id = TraceId::Rendered(trace_id.into())
112 } else if let Some(parent_id) = line.strip_prefix("Parent=") {
113 header.parent_id = Some(SegmentId::Rendered(parent_id.into()))
114 } else if line.starts_with("Sampled=") {
115 header.sampling_decision = line.into();
116 } else if !line.starts_with("Self=") {
117 let (key, value) = line
118 .split_once('=')
119 .ok_or_else(|| format!("invalid key=value: no `=` found in `{}`", s))?;
120 header.additional_data.insert(key.into(), value.into());
121 }
122 Ok(header)
123 })
124 }
125}
126
127impl Display for Header {
128 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129 write!(f, "Root={}", self.trace_id)?;
130 if let Some(parent) = &self.parent_id {
131 write!(f, ";Parent={}", parent)?;
132 }
133 if self.sampling_decision != SamplingDecision::Unknown {
134 write!(f, ";{}", self.sampling_decision)?;
135 }
136 for (k, v) in &self.additional_data {
137 write!(f, ";{}={}", k, v)?;
138 }
139 Ok(())
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 #[test]
147 fn parse_with_parent_from_str() {
148 assert_eq!(
149 "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"
150 .parse::<Header>(),
151 Ok(Header {
152 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
153 parent_id: Some(SegmentId::Rendered("53995c3f42cd8ad8".into())),
154 sampling_decision: SamplingDecision::Sampled,
155 ..Header::default()
156 })
157 )
158 }
159 #[test]
160 fn parse_no_parent_from_str() {
161 assert_eq!(
162 "Root=1-5759e988-bd862e3fe1be46a994272793;Sampled=1".parse::<Header>(),
163 Ok(Header {
164 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
165 parent_id: None,
166 sampling_decision: SamplingDecision::Sampled,
167 ..Header::default()
168 })
169 )
170 }
171 #[test]
172 fn parse_with_additional_data_from_str() {
173 assert_eq!(
174 "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1;Lineage=01234567:0;Unknown=unknown"
175 .parse::<Header>(),
176 Ok(Header {
177 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
178 parent_id: Some(SegmentId::Rendered("53995c3f42cd8ad8".into())),
179 sampling_decision: SamplingDecision::Sampled,
180 additional_data: vec![
181 ("Lineage".into(), "01234567:0".into()),
182 ("Unknown".into(), "unknown".into()),
183 ].into_iter().collect()
184 })
185 )
186 }
187
188 #[test]
189 fn displays_as_header() {
190 let header = Header {
191 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
192 ..Header::default()
193 };
194 assert_eq!(
195 format!("{}", header),
196 "Root=1-5759e988-bd862e3fe1be46a994272793"
197 );
198 }
199
200 #[test]
201 fn replace_parent_id() {
202 let header = Header {
203 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
204 parent_id: Some(SegmentId::Rendered("53995c3f42cd8ad8".into())),
205 sampling_decision: SamplingDecision::Sampled,
206 ..Header::default()
207 };
208 assert_eq!(
209 header.with_parent_id(SegmentId::Rendered("35b167406b7746cf".into())),
210 Header {
211 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
212 parent_id: Some(SegmentId::Rendered("35b167406b7746cf".into())),
213 sampling_decision: SamplingDecision::Sampled,
214 ..Header::default()
215 },
216 );
217 }
218
219 #[test]
220 fn replace_sampling_decision() {
221 let header = Header {
222 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
223 parent_id: Some(SegmentId::Rendered("53995c3f42cd8ad8".into())),
224 sampling_decision: SamplingDecision::Sampled,
225 ..Header::default()
226 };
227 assert_eq!(
228 header.with_sampling_decision(SamplingDecision::NotSampled),
229 Header {
230 trace_id: TraceId::Rendered("1-5759e988-bd862e3fe1be46a994272793".into()),
231 parent_id: Some(SegmentId::Rendered("53995c3f42cd8ad8".into())),
232 sampling_decision: SamplingDecision::NotSampled,
233 ..Header::default()
234 },
235 );
236 }
237}