http_types/trace/server_timing/
metric.rs

1use std::time::Duration;
2
3use crate::headers::HeaderValue;
4
5/// An individual entry into `ServerTiming`.
6//
7// # Implementation notes
8//
9// Four different cases are valid:
10//
11// 1. metric name only       cache
12// 2. metric + value         cache;dur=2.4
13// 3. metric + desc          cache;desc="Cache Read"
14// 4. metric + value + desc  cache;desc="Cache Read";dur=23.2
15//
16// Multiple different entries per line are supported; separated with a `,`.
17#[derive(Debug, Clone, Eq, PartialEq)]
18pub struct Metric {
19    pub(crate) name: String,
20    pub(crate) dur: Option<Duration>,
21    pub(crate) desc: Option<String>,
22}
23
24impl Metric {
25    /// Create a new instance of `Metric`.
26    ///
27    /// # Errors
28    ///
29    /// An error will be returned if the string values are invalid ASCII.
30    pub fn new(name: String, dur: Option<Duration>, desc: Option<String>) -> crate::Result<Self> {
31        crate::ensure!(name.is_ascii(), "Name should be valid ASCII");
32        if let Some(desc) = desc.as_ref() {
33            crate::ensure!(desc.is_ascii(), "Description should be valid ASCII");
34        };
35
36        Ok(Self { name, dur, desc })
37    }
38
39    /// The timing name.
40    pub fn name(&self) -> &String {
41        &self.name
42    }
43
44    /// The timing duration.
45    pub fn duration(&self) -> Option<Duration> {
46        self.dur
47    }
48
49    /// The timing description.
50    pub fn description(&self) -> Option<&str> {
51        self.desc.as_deref()
52    }
53}
54
55impl From<Metric> for HeaderValue {
56    fn from(entry: Metric) -> HeaderValue {
57        let mut string = entry.name;
58
59        // Format a `Duration` into the format that the spec expects.
60        let f = |d: Duration| d.as_secs_f64() * 1000.0;
61
62        match (entry.dur, entry.desc) {
63            (Some(dur), Some(desc)) => {
64                string.push_str(&format!("; dur={}; desc=\"{}\"", f(dur), desc))
65            }
66            (Some(dur), None) => string.push_str(&format!("; dur={}", f(dur))),
67            (None, Some(desc)) => string.push_str(&format!("; desc=\"{}\"", desc)),
68            (None, None) => {}
69        };
70
71        // SAFETY: we validate that the values are valid ASCII on creation.
72        unsafe { HeaderValue::from_bytes_unchecked(string.into_bytes()) }
73    }
74}
75
76#[cfg(test)]
77mod test {
78    use super::*;
79    use crate::headers::HeaderValue;
80    use std::time::Duration;
81
82    #[test]
83    #[allow(clippy::redundant_clone)]
84    fn encode() -> crate::Result<()> {
85        let name = String::from("Server");
86        let dur = Duration::from_secs(1);
87        let desc = String::from("A server timing");
88
89        let val: HeaderValue = Metric::new(name.clone(), None, None)?.into();
90        assert_eq!(val, "Server");
91
92        let val: HeaderValue = Metric::new(name.clone(), Some(dur), None)?.into();
93        assert_eq!(val, "Server; dur=1000");
94
95        let val: HeaderValue = Metric::new(name.clone(), None, Some(desc.clone()))?.into();
96        assert_eq!(val, r#"Server; desc="A server timing""#);
97
98        let val: HeaderValue = Metric::new(name.clone(), Some(dur), Some(desc.clone()))?.into();
99        assert_eq!(val, r#"Server; dur=1000; desc="A server timing""#);
100        Ok(())
101    }
102}