1
+ use std:: str:: FromStr ;
1
2
use std:: time:: Duration ;
2
3
3
4
use crate :: headers:: HeaderValue ;
5
+ use crate :: Status ;
6
+ use crate :: { ensure, format_err} ;
4
7
5
8
/// An individual `ServerTiming` entry.
6
9
//
@@ -47,8 +50,8 @@ impl Entry {
47
50
}
48
51
49
52
/// The timing description.
50
- pub fn description ( & self ) -> Option < & String > {
51
- self . desc . as_ref ( )
53
+ pub fn description ( & self ) -> Option < & str > {
54
+ self . desc . as_ref ( ) . map ( |s| s . as_str ( ) )
52
55
}
53
56
}
54
57
@@ -73,12 +76,93 @@ impl From<Entry> for HeaderValue {
73
76
}
74
77
}
75
78
79
+ impl FromStr for Entry {
80
+ type Err = crate :: Error ;
81
+ // Create an entry from a string. Parsing rules in ABNF are:
82
+ //
83
+ // ```
84
+ // Server-Timing = #server-timing-metric
85
+ // server-timing-metric = metric-name *( OWS ";" OWS server-timing-param )
86
+ // metric-name = token
87
+ // server-timing-param = server-timing-param-name OWS "=" OWS server-timing-param-value
88
+ // server-timing-param-name = token
89
+ // server-timing-param-value = token / quoted-string
90
+ // ```
91
+ //
92
+ // Source: https://w3c.github.io/server-timing/#the-server-timing-header-field
93
+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
94
+ let mut parts = s. trim ( ) . split ( ';' ) ;
95
+
96
+ // Get the name. This is non-optional.
97
+ let name = parts
98
+ . next ( )
99
+ . ok_or_else ( || format_err ! ( "Server timing headers must include a name" ) ) ?
100
+ . trim_end ( ) ;
101
+
102
+ // We must extract these values from the k-v pairs that follow.
103
+ let mut dur = None ;
104
+ let mut desc = None ;
105
+
106
+ for mut part in parts {
107
+ ensure ! (
108
+ !part. is_empty( ) ,
109
+ "Server timing params cannot end with a trailing `;`"
110
+ ) ;
111
+
112
+ part = part. trim_start ( ) ;
113
+
114
+ let mut params = part. split ( '=' ) ;
115
+ let name = params
116
+ . next ( )
117
+ . ok_or_else ( || format_err ! ( "Server timing params must have a name" ) ) ?
118
+ . trim_end ( ) ;
119
+ let mut value = params
120
+ . next ( )
121
+ . ok_or_else ( || format_err ! ( "Server timing params must have a value" ) ) ?
122
+ . trim_start ( ) ;
123
+
124
+ match name {
125
+ "dur" => {
126
+ let millis: f64 = value. parse ( ) . status ( 400 ) . map_err ( |_| {
127
+ format_err ! ( "Server timing duration params must be a valid double-precision floating-point number." )
128
+ } ) ?;
129
+ dur = Some ( Duration :: from_secs_f64 ( millis / 1000.0 ) ) ;
130
+ }
131
+ "desc" => {
132
+ // Ensure quotes line up, and strip them from the resulting output
133
+ if value. starts_with ( '"' ) {
134
+ value = & value[ 1 ..value. len ( ) ] ;
135
+ ensure ! (
136
+ value. ends_with( '"' ) ,
137
+ "Server timing description params must use matching quotes"
138
+ ) ;
139
+ value = & value[ 0 ..value. len ( ) - 1 ] ;
140
+ } else {
141
+ ensure ! (
142
+ !value. ends_with( '"' ) ,
143
+ "Server timing description params must use matching quotes"
144
+ ) ;
145
+ }
146
+ desc = Some ( value. to_string ( ) ) ;
147
+ }
148
+ _ => continue ,
149
+ }
150
+ }
151
+
152
+ Ok ( Entry {
153
+ name : name. to_string ( ) ,
154
+ dur,
155
+ desc,
156
+ } )
157
+ }
158
+ }
159
+
76
160
#[ cfg( test) ]
77
161
mod test {
78
162
use super :: * ;
79
163
80
164
#[ test]
81
- fn create_header_value ( ) -> crate :: Result < ( ) > {
165
+ fn encode ( ) -> crate :: Result < ( ) > {
82
166
let name = String :: from ( "Server" ) ;
83
167
let dur = Duration :: from_secs ( 1 ) ;
84
168
let desc = String :: from ( "A server timing" ) ;
@@ -89,8 +173,78 @@ mod test {
89
173
let val: HeaderValue = Entry :: new ( name. clone ( ) , Some ( dur) , None ) ?. into ( ) ;
90
174
assert_eq ! ( val, "Server; dur=1000" ) ;
91
175
176
+ let val: HeaderValue = Entry :: new ( name. clone ( ) , None , Some ( desc. clone ( ) ) ) ?. into ( ) ;
177
+ assert_eq ! ( val, r#"Server; desc="A server timing""# ) ;
178
+
92
179
let val: HeaderValue = Entry :: new ( name. clone ( ) , Some ( dur) , Some ( desc. clone ( ) ) ) ?. into ( ) ;
93
180
assert_eq ! ( val, r#"Server; dur=1000; desc="A server timing""# ) ;
94
181
Ok ( ( ) )
95
182
}
183
+
184
+ #[ test]
185
+ fn decode ( ) -> crate :: Result < ( ) > {
186
+ // Metric name only.
187
+ assert_entry ( "Server" , "Server" , None , None ) ?;
188
+ assert_entry ( "Server " , "Server" , None , None ) ?;
189
+ assert_entry_err (
190
+ "Server ;" ,
191
+ "Server timing params cannot end with a trailing `;`" ,
192
+ ) ;
193
+ assert_entry_err (
194
+ "Server; " ,
195
+ "Server timing params cannot end with a trailing `;`" ,
196
+ ) ;
197
+
198
+ // Metric name + param
199
+ assert_entry ( "Server; dur=1000" , "Server" , Some ( 1000 ) , None ) ?;
200
+ assert_entry ( "Server; dur =1000" , "Server" , Some ( 1000 ) , None ) ?;
201
+ assert_entry ( "Server; dur= 1000" , "Server" , Some ( 1000 ) , None ) ?;
202
+ assert_entry ( "Server; dur = 1000" , "Server" , Some ( 1000 ) , None ) ?;
203
+ assert_entry_err (
204
+ "Server; dur=1000;" ,
205
+ "Server timing params cannot end with a trailing `;`" ,
206
+ ) ;
207
+
208
+ // Metric name + desc
209
+ assert_entry ( r#"DB; desc="a db""# , "DB" , None , Some ( "a db" ) ) ?;
210
+ assert_entry ( r#"DB; desc ="a db""# , "DB" , None , Some ( "a db" ) ) ?;
211
+ assert_entry ( r#"DB; desc= "a db""# , "DB" , None , Some ( "a db" ) ) ?;
212
+ assert_entry ( r#"DB; desc = "a db""# , "DB" , None , Some ( "a db" ) ) ?;
213
+ assert_entry ( r#"DB; desc=a_db"# , "DB" , None , Some ( "a_db" ) ) ?;
214
+ assert_entry_err (
215
+ r#"DB; desc="db"# ,
216
+ "Server timing description params must use matching quotes" ,
217
+ ) ;
218
+ assert_entry_err (
219
+ "Server; desc=a_db;" ,
220
+ "Server timing params cannot end with a trailing `;`" ,
221
+ ) ;
222
+
223
+ // Metric name + dur + desc
224
+ assert_entry (
225
+ r#"Server; dur=1000; desc="a server""# ,
226
+ "Server" ,
227
+ Some ( 1000 ) ,
228
+ Some ( "a server" ) ,
229
+ ) ?;
230
+ assert_entry_err (
231
+ r#"Server; dur=1000; desc="a server";"# ,
232
+ "Server timing params cannot end with a trailing `;`" ,
233
+ ) ;
234
+ Ok ( ( ) )
235
+ }
236
+
237
+ fn assert_entry_err ( s : & str , msg : & str ) {
238
+ let err = Entry :: from_str ( s) . unwrap_err ( ) ;
239
+ assert_eq ! ( format!( "{}" , err) , msg) ;
240
+ }
241
+
242
+ /// Assert an entry and all of its fields.
243
+ fn assert_entry ( s : & str , n : & str , du : Option < u64 > , de : Option < & str > ) -> crate :: Result < ( ) > {
244
+ let e = Entry :: from_str ( s) ?;
245
+ assert_eq ! ( e. name( ) , n) ;
246
+ assert_eq ! ( e. duration( ) , du. map( |du| Duration :: from_millis( du) ) ) ;
247
+ assert_eq ! ( e. description( ) , de) ;
248
+ Ok ( ( ) )
249
+ }
96
250
}
0 commit comments