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