http_types/trace/server_timing/
parse.rs1use std::time::Duration;
2
3use super::Metric;
4use crate::{ensure, format_err, StatusCode};
5
6pub(super) fn parse_header(s: &str, entries: &mut Vec<Metric>) -> crate::Result<()> {
10 for part in s.trim().split(',') {
11 let entry = parse_entry(part).map_err(|mut e| {
12 e.set_status(StatusCode::BadRequest);
13 e
14 })?;
15 entries.push(entry);
16 }
17 Ok(())
18}
19
20fn parse_entry(s: &str) -> crate::Result<Metric> {
33 let mut parts = s.trim().split(';');
34
35 let name = parts
37 .next()
38 .ok_or_else(|| format_err!("Server timing headers must include a name"))?
39 .trim_end();
40
41 let mut dur = None;
43 let mut desc = None;
44
45 for mut part in parts {
46 ensure!(
47 !part.is_empty(),
48 "Server timing params cannot end with a trailing `;`"
49 );
50
51 part = part.trim_start();
52
53 let mut params = part.split('=');
54 let name = params
55 .next()
56 .ok_or_else(|| format_err!("Server timing params must have a name"))?
57 .trim_end();
58 let mut value = params
59 .next()
60 .ok_or_else(|| format_err!("Server timing params must have a value"))?
61 .trim_start();
62
63 match name {
64 "dur" => {
65 let millis: f64 = value.parse().map_err(|_| {
66 format_err!("Server timing duration params must be a valid double-precision floating-point number.")
67 })?;
68 dur = Some(Duration::from_secs_f64(millis / 1000.0));
69 }
70 "desc" => {
71 if value.starts_with('"') {
73 value = &value[1..value.len()];
74 ensure!(
75 value.ends_with('"'),
76 "Server timing description params must use matching quotes"
77 );
78 value = &value[0..value.len() - 1];
79 } else {
80 ensure!(
81 !value.ends_with('"'),
82 "Server timing description params must use matching quotes"
83 );
84 }
85 desc = Some(value.to_string());
86 }
87 _ => continue,
88 }
89 }
90
91 Ok(Metric {
92 name: name.to_string(),
93 dur,
94 desc,
95 })
96}
97
98#[cfg(test)]
99mod test {
100 use super::*;
101
102 #[test]
103 fn decode_header() -> crate::Result<()> {
104 assert_entry("Server", "Server", None, None)?;
106 assert_entry("Server ", "Server", None, None)?;
107 assert_entry_err(
108 "Server ;",
109 "Server timing params cannot end with a trailing `;`",
110 );
111 assert_entry_err(
112 "Server; ",
113 "Server timing params cannot end with a trailing `;`",
114 );
115
116 assert_entry("Server; dur=1000", "Server", Some(1000), None)?;
118 assert_entry("Server; dur =1000", "Server", Some(1000), None)?;
119 assert_entry("Server; dur= 1000", "Server", Some(1000), None)?;
120 assert_entry("Server; dur = 1000", "Server", Some(1000), None)?;
121 assert_entry_err(
122 "Server; dur=1000;",
123 "Server timing params cannot end with a trailing `;`",
124 );
125
126 assert_entry(r#"DB; desc="a db""#, "DB", None, Some("a db"))?;
128 assert_entry(r#"DB; desc ="a db""#, "DB", None, Some("a db"))?;
129 assert_entry(r#"DB; desc= "a db""#, "DB", None, Some("a db"))?;
130 assert_entry(r#"DB; desc = "a db""#, "DB", None, Some("a db"))?;
131 assert_entry(r#"DB; desc=a_db"#, "DB", None, Some("a_db"))?;
132 assert_entry_err(
133 r#"DB; desc="db"#,
134 "Server timing description params must use matching quotes",
135 );
136 assert_entry_err(
137 "Server; desc=a_db;",
138 "Server timing params cannot end with a trailing `;`",
139 );
140
141 assert_entry(
143 r#"Server; dur=1000; desc="a server""#,
144 "Server",
145 Some(1000),
146 Some("a server"),
147 )?;
148 assert_entry_err(
149 r#"Server; dur=1000; desc="a server";"#,
150 "Server timing params cannot end with a trailing `;`",
151 );
152 Ok(())
153 }
154
155 #[test]
156 fn decode_headers() -> crate::Result<()> {
157 let mut entries = vec![];
159 parse_header("db;dur=53, app;dur=47.2", &mut entries)?;
160 let e = &entries[0];
161 assert_eq!(e.name(), "db");
162 assert_eq!(e.duration(), Some(Duration::from_millis(53)));
163 let e = &entries[1];
164 assert_eq!(e.name(), "app");
165 assert_eq!(e.duration(), Some(Duration::from_micros(47200)));
166 Ok(())
167 }
168
169 fn assert_entry_err(s: &str, msg: &str) {
170 let err = parse_entry(s).unwrap_err();
171 assert_eq!(format!("{}", err), msg);
172 }
173
174 fn assert_entry(s: &str, n: &str, du: Option<u64>, de: Option<&str>) -> crate::Result<()> {
176 let e = parse_entry(s)?;
177 assert_eq!(e.name(), n);
178 assert_eq!(e.duration(), du.map(Duration::from_millis));
179 assert_eq!(e.description(), de);
180 Ok(())
181 }
182}