/Users/oliverbrotchie/Documents/GitHub/csv-ledger/src/parse.rs
Line | Count | Source |
1 | | //! # Zero-coppy CSV Parsing |
2 | | //! Validate headers and parse transactions from text. |
3 | | //! |
4 | | //! **Basic example:** |
5 | | //! ```rust |
6 | | //! use crate::parse::{parse_header, parse_transaction} |
7 | | //! |
8 | | //! fn main() { |
9 | | //! // Example csv data |
10 | | //! let csv = "type, client, tx, amount, |
11 | | //! deposit, 1, 1, 17.99 |
12 | | //! withdrawal, 2, 2, 12.00 |
13 | | //! hold 1, 1, "; |
14 | | //! |
15 | | //! let mut lines = csv.split("\n"); |
16 | | //! let mut transactions = Vec::new(); |
17 | | //! |
18 | | //! // Validate that the header is in the correct format |
19 | | //! parse_header(lines.next().unwrap()).expect("Header was invalid."); |
20 | | //! |
21 | | //! // Insert all transactions into a vector |
22 | | //! for line in lines { |
23 | | //! transactions.push(parse_transaction(line).expect("Transaction was invalid.")); |
24 | | //! } |
25 | | //! |
26 | | //! // Print out the vector |
27 | | //! println!("{:?}", transactions); |
28 | | //! } |
29 | | //! ``` |
30 | | |
31 | | extern crate nom; |
32 | | |
33 | | use nom::{ |
34 | | branch::alt, |
35 | | bytes::complete::{tag, take_while, take_while_m_n}, |
36 | | character::{ |
37 | | complete::{multispace0, u16, u32}, |
38 | | is_digit, |
39 | | }, |
40 | | error::{Error as SubErr, ErrorKind, ParseError}, |
41 | | sequence::{delimited, terminated}, |
42 | | Err as NomErr, IResult, |
43 | | }; |
44 | | |
45 | | /// An enum that represents possible transaction types. |
46 | 14 | #[derive(D5 ebug5 , PartialEq, Eq)] Unexecuted instantiation: <csv_ledger::parse::Transaction as core::cmp::PartialEq>::ne <csv_ledger::parse::Transaction as core::cmp::PartialEq>::eq Line | Count | Source | 46 | 14 | #[derive(Debug, PartialEq, Eq)] |
|
47 | | pub enum Transaction { |
48 | | Deposit(u16, u32, i64), |
49 | | Withdrawal(u16, u32, i64), |
50 | | Dispute(u16, u32), |
51 | | Resolve(u16, u32), |
52 | | Chargeback(u16, u32), |
53 | | } |
54 | | |
55 | | /// A helper function to construct nom errors from custom strings. |
56 | 22 | pub fn nom_err(input: &str) -> NomErr<SubErr<&str>> { |
57 | 22 | NomErr::Failure(SubErr { |
58 | 22 | input, |
59 | 22 | code: ErrorKind::Fail, |
60 | 22 | }) |
61 | 22 | } |
62 | | |
63 | | /// A parser that ignores whitespace around the input parser. |
64 | 168 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( |
65 | 168 | inner: F, |
66 | 168 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> |
67 | 168 | where |
68 | 168 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, |
69 | 168 | { |
70 | 168 | delimited(multispace0, inner, multispace0) |
71 | 168 | } csv_ledger::parse::ws::<nom::bytes::complete::tag<&str, &str, ()>::{closure#0}, &str, ()>Line | Count | Source | 64 | 1 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 1 | inner: F, | 66 | 1 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 1 | where | 68 | 1 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 1 | { | 70 | 1 | delimited(multispace0, inner, multispace0) | 71 | 1 | } |
csv_ledger::parse::ws::<nom::character::complete::u32<&str, nom::error::Error<&str>>, u32, nom::error::Error<&str>> Line | Count | Source | 64 | 30 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 30 | inner: F, | 66 | 30 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 30 | where | 68 | 30 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 30 | { | 70 | 30 | delimited(multispace0, inner, multispace0) | 71 | 30 | } |
csv_ledger::parse::ws::<nom::branch::alt<&str, &str, nom::error::Error<&str>, (nom::bytes::complete::tag<&str, &str, nom::error::Error<&str>>::{closure#0}, nom::bytes::complete::tag<&str, &str, nom::error::Error<&str>>::{closure#0}, nom::bytes::complete::tag<&str, &str, nom::error::Error<&str>>::{closure#0}, nom::bytes::complete::tag<&str, &str, nom::error::Error<&str>>::{closure#0}, nom::bytes::complete::tag<&str, &str, nom::error::Error<&str>>::{closure#0})>::{closure#0}, &str, nom::error::Error<&str>>Line | Count | Source | 64 | 34 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 34 | inner: F, | 66 | 34 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 34 | where | 68 | 34 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 34 | { | 70 | 34 | delimited(multispace0, inner, multispace0) | 71 | 34 | } |
csv_ledger::parse::ws::<nom::bytes::complete::tag<&str, &str, nom::error::Error<&str>>::{closure#0}, &str, nom::error::Error<&str>>Line | Count | Source | 64 | 70 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 70 | inner: F, | 66 | 70 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 70 | where | 68 | 70 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 70 | { | 70 | 70 | delimited(multispace0, inner, multispace0) | 71 | 70 | } |
csv_ledger::parse::ws::<nom::bytes::complete::tag<&str, &str, (&str, nom::error::ErrorKind)>::{closure#0}, &str, (&str, nom::error::ErrorKind)>Line | Count | Source | 64 | 1 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 1 | inner: F, | 66 | 1 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 1 | where | 68 | 1 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 1 | { | 70 | 1 | delimited(multispace0, inner, multispace0) | 71 | 1 | } |
csv_ledger::parse::ws::<nom::character::complete::u16<&str, nom::error::Error<&str>>, u16, nom::error::Error<&str>> Line | Count | Source | 64 | 32 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 32 | inner: F, | 66 | 32 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 32 | where | 68 | 32 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 32 | { | 70 | 32 | delimited(multispace0, inner, multispace0) | 71 | 32 | } |
|
72 | | |
73 | | /// Test if a character is a digit. |
74 | 105 | pub fn digit(chr: char) -> bool { |
75 | 105 | chr.is_ascii() && is_digit(chr as u8) |
76 | 105 | } |
77 | | |
78 | | /// Parse a i64 number from a string, optionally allowing a maximum number of digits to be specified. |
79 | 50 | pub fn double(input: &str, max: Option<usize>) -> IResult<&str, i64> { |
80 | 50 | let (input, num49 ) = match max { |
81 | 17 | Some(m) => take_while_m_n(1, m, digit)(input), |
82 | 33 | None => take_while(digit)(input), |
83 | 1 | }?; |
84 | | |
85 | | // Convert the string to i64 |
86 | | Ok(( |
87 | 49 | input, |
88 | 49 | num.parse::<i64>() |
89 | 49 | .map_err(|_| nom_err("Could not parse number as i64.")14 )?14 , |
90 | | )) |
91 | 50 | } |
92 | | |
93 | | #[inline] |
94 | | /// Parse an up to four decimal place number as an i64 by multiplying by 10000. |
95 | 33 | pub fn four_dp(input: &str) -> IResult<&str, i64> { |
96 | 33 | let (input, pre_dp19 ) = double(input, None)?14 ; |
97 | | |
98 | | // Optionally parse decimal places |
99 | 19 | if let Ok((input17 , _)) = tag::<_, _, (&str, ErrorKind)>(".")(input) { |
100 | 17 | let (input, post_dp16 ) = double(input, Some(4))?1 ; |
101 | | |
102 | | // Convert decimal places to whole numbers |
103 | 16 | return Ok(( |
104 | 16 | input, |
105 | 16 | (pre_dp * 10000 + post_dp * 10_i64.pow(3 - (post_dp as f32).log10() as u32)), |
106 | 16 | )); |
107 | 2 | } |
108 | 2 | |
109 | 2 | Ok((input, (pre_dp * 10000))) |
110 | 33 | } |
111 | | |
112 | | /// Parse a line of the CSV as a Transaction. |
113 | | /// Please note that whitespace will be ignored. |
114 | | /// |
115 | | /// Example: |
116 | | /// ```ts |
117 | | /// // Valid Inputs: |
118 | | /// assert_eq!(parse_transaction("deposit, 1, 1, 20.0"), ("", Transaction::Deposit(1, 1, 200))) |
119 | | /// assert_eq!(parse_transaction(" deposit, 2, 20 ,6.99 "), ("", Transaction::Deposit(2, 20, 699))); |
120 | | /// assert_eq!(parse_transaction("withdrawal, 3, 7, 22.7"), ("", Transaction::Withdrawal(3, 7, 2270))); |
121 | | /// |
122 | | /// assert_eq!(parse_transaction("dispute, 2, 2,"), ("", Transaction::Dispute(2, 2))); |
123 | | /// assert_eq!(parse_transaction("resolve, 2, 2,"), ("", Transaction::Resolve(2, 2))); |
124 | | /// |
125 | | /// assert_eq!(parse_transaction("dispute, 3, 7,"), ("", Transaction::Dispute(3, 7))); |
126 | | /// assert_eq!(parse_transaction("chargeback, 3, 7,"), ("", Transaction::Chargeback(3, 7))); |
127 | | /// |
128 | | /// // Invalid Inputs: |
129 | | /// assert!(parse_transaction("deposit, 1, 1,").is_err()); |
130 | | /// assert!(parse_transaction("xyz, 1, 1, 2.0").is_err()); |
131 | | /// assert!(parse_transaction("dispute, 1,").is_err()); |
132 | | /// ``` |
133 | | #[inline] |
134 | 34 | pub fn parse_transaction(input: &str) -> Result<Transaction, NomErr<SubErr<&str>>> { |
135 | | // Parse the type of Transaction |
136 | 34 | let (input, key32 ) = terminated( |
137 | 34 | ws(alt(( |
138 | 34 | tag("deposit"), |
139 | 34 | tag("withdrawal"), |
140 | 34 | tag("dispute"), |
141 | 34 | tag("resolve"), |
142 | 34 | tag("chargeback"), |
143 | 34 | ))), |
144 | 34 | tag(","), |
145 | 34 | )(input)?2 ; |
146 | | |
147 | | // Parse the account and Transaction ID |
148 | 32 | let (input, client30 ) = terminated(ws(u16), tag(","))(input)?2 ; |
149 | 30 | let (input, tx28 ) = terminated(ws(u32), tag(","))(input)?2 ; |
150 | | |
151 | | // Parse the Transaction amount |
152 | 28 | let amount = delimited(multispace0, four_dp, multispace0)(input).ok(); |
153 | | |
154 | | // Check that the line has been consumed completely |
155 | 28 | if let Some((input15 , _)) = amount { |
156 | 15 | if !input.is_empty() { |
157 | 1 | Err(nom_err("Input was not empty after parsing transaction."))?; |
158 | 14 | } |
159 | 13 | } |
160 | | |
161 | | // Convert result into Transaction |
162 | 27 | Ok(match (key, amount) { |
163 | 27 | ("deposit", Some((_, value))) => Transaction::Deposit(client, tx, value)11 , |
164 | 16 | ("withdrawal", Some((_, value))) => Transaction::Withdrawal(client, tx, value)2 , |
165 | 14 | ("dispute", None) => Transaction::Dispute(client, tx)4 , |
166 | 10 | ("resolve", None) => Transaction::Resolve(client, tx)2 , |
167 | 8 | ("chargeback", None) => Transaction::Chargeback(client, tx)2 , |
168 | 6 | (_, _) => Err(nom_err(if key == "deposit" || key == "withdrawal"2 { |
169 | 5 | "Deposit or Withdrawal with a missing or invalid amount." |
170 | | } else { |
171 | 1 | "Dispute, Resolve or Chargeback with an amount." |
172 | 6 | }))?, |
173 | | }) |
174 | 34 | } |
175 | | |
176 | | /// Parse the CSV header to validate that the CSV is in the correct format. |
177 | | /// Please note that whitespace will be ignored. |
178 | | /// |
179 | | /// Example: |
180 | | /// ```rs |
181 | | /// assert!(parse_header("type, client, tx, amount").is_ok()); |
182 | | /// assert!(parse_header(" type, client, tx ,amount ").is_ok()); |
183 | | /// |
184 | | /// assert!(parse_header("type, client, tx").is_err()); |
185 | | /// ``` |
186 | | #[inline] |
187 | 24 | pub fn parse_header(input: &str) -> Result<(), NomErr<SubErr<&str>>> { |
188 | 24 | let (input17 , _) = terminated(ws(tag("type")), tag(","))(input)?7 ; |
189 | 17 | let (input15 , _) = terminated(ws(tag("client")), tag(","))(input)?2 ; |
190 | 15 | let (input14 , _) = terminated(ws(tag("tx")), tag(","))(input)?1 ; |
191 | 14 | let (input12 , _) = ws(tag("amount"))(input)?2 ; |
192 | | |
193 | 12 | if !input.is_empty() { |
194 | 1 | return Err(nom_err("Input was not empty after parsing transaction.")); |
195 | 11 | } |
196 | 11 | |
197 | 11 | Ok(()) |
198 | 24 | } |
199 | | |
200 | | #[cfg(test)] |
201 | | mod parse_transaction { |
202 | | use crate::parse::{parse_transaction, Transaction}; |
203 | | |
204 | 1 | #[test] |
205 | 1 | fn deposit() { |
206 | 1 | let res = parse_transaction("deposit, 1, 2, 3.1").unwrap(); |
207 | 1 | assert_eq!(res, Transaction::Deposit(1, 2, 31000)); |
208 | 1 | } |
209 | | |
210 | 1 | #[test] |
211 | 1 | fn withdrawal() { |
212 | 1 | let res = parse_transaction("withdrawal, 1, 2, 3.0").unwrap(); |
213 | 1 | assert_eq!(res, Transaction::Withdrawal(1, 2, 30000)); |
214 | 1 | } |
215 | | |
216 | 1 | #[test] |
217 | 1 | fn dispute() { |
218 | 1 | let res = parse_transaction("dispute, 1, 2,").unwrap(); |
219 | 1 | assert_eq!(res, Transaction::Dispute(1, 2)); |
220 | 1 | } |
221 | | |
222 | 1 | #[test] |
223 | 1 | fn resolve() { |
224 | 1 | let res = parse_transaction("resolve, 1, 2,").unwrap(); |
225 | 1 | assert_eq!(res, Transaction::Resolve(1, 2)); |
226 | 1 | } |
227 | | |
228 | 1 | #[test] |
229 | 1 | fn chargeback() { |
230 | 1 | let res = parse_transaction("chargeback, 1, 2,").unwrap(); |
231 | 1 | assert_eq!(res, Transaction::Chargeback(1, 2)); |
232 | 1 | } |
233 | | |
234 | 1 | #[test] |
235 | 1 | fn ok_no_decimal() { |
236 | 1 | let res = parse_transaction("deposit, 1, 2, 3").unwrap(); |
237 | 1 | assert_eq!(res, Transaction::Deposit(1, 2, 30000)); |
238 | 1 | } |
239 | | |
240 | 1 | #[test] |
241 | 1 | fn ok_no_white_space() { |
242 | 1 | let res = parse_transaction("deposit,1,2,3.0").unwrap(); |
243 | 1 | |
244 | 1 | assert_eq!(res, Transaction::Deposit(1, 2, 30000)); |
245 | 1 | } |
246 | | |
247 | 1 | #[test] |
248 | 1 | fn ok_with_white_space() { |
249 | 1 | let res = parse_transaction(" deposit ,1 , 2, 3.0 ").unwrap(); |
250 | 1 | assert_eq!(res, Transaction::Deposit(1, 2, 30000)); |
251 | 1 | } |
252 | | |
253 | 1 | #[test] |
254 | 1 | fn ok_no_amount() { |
255 | 1 | let res = parse_transaction("dispute,1,2,").unwrap(); |
256 | 1 | assert_eq!(res, Transaction::Dispute(1, 2)); |
257 | 1 | } |
258 | | |
259 | 1 | #[test] |
260 | 1 | fn err_parser_runthrough() { |
261 | 1 | parse_transaction("x").unwrap_err(); |
262 | 1 | parse_transaction("deposit,x").unwrap_err(); |
263 | 1 | parse_transaction("deposit,1,x").unwrap_err(); |
264 | 1 | parse_transaction("deposit,1,2,x").unwrap_err(); |
265 | 1 | parse_transaction(&format!("deposit,1,2,2{}", f32::MAX)).unwrap_err(); |
266 | 1 | } |
267 | | |
268 | 1 | #[test] |
269 | 1 | fn err_invalid_u16() { |
270 | 1 | parse_transaction("deposit,65536,2,3.0").unwrap_err(); |
271 | 1 | } |
272 | | |
273 | 1 | #[test] |
274 | 1 | fn err_invalid_deposit() { |
275 | 1 | parse_transaction("deposit,1,2,").unwrap_err(); |
276 | 1 | } |
277 | | |
278 | 1 | #[test] |
279 | 1 | fn err_dispute_missing_value() { |
280 | 1 | parse_transaction("dispute,1,").unwrap_err(); |
281 | 1 | } |
282 | | |
283 | 1 | #[test] |
284 | 1 | fn err_withdrawal_missing_value() { |
285 | 1 | let res = parse_transaction("withdrawal,1,2,").unwrap_err(); |
286 | 1 | assert_eq!( |
287 | 1 | res.to_string(), |
288 | 1 | "Parsing Failure: Error { input: \"Deposit or Withdrawal with a missing or invalid amount.\", code: Fail }" |
289 | 1 | ); |
290 | 1 | } |
291 | | |
292 | 1 | #[test] |
293 | 1 | fn err_deposit_missing_value() { |
294 | 1 | let res = parse_transaction("deposit,1,2,").unwrap_err(); |
295 | 1 | assert_eq!( |
296 | 1 | res.to_string(), |
297 | 1 | "Parsing Failure: Error { input: \"Deposit or Withdrawal with a missing or invalid amount.\", code: Fail }" |
298 | 1 | ); |
299 | 1 | } |
300 | | |
301 | 1 | #[test] |
302 | 1 | fn err_dispute_extra_value() { |
303 | 1 | let res = parse_transaction("dispute,1,2,3.0").unwrap_err(); |
304 | 1 | |
305 | 1 | assert_eq!( |
306 | 1 | res.to_string(), |
307 | 1 | "Parsing Failure: Error { input: \"Dispute, Resolve or Chargeback with an amount.\", code: Fail }" |
308 | 1 | ); |
309 | 1 | } |
310 | | |
311 | 1 | #[test] |
312 | 1 | fn err_extra_value() { |
313 | 1 | parse_transaction("withdrawal,1,2,3.0,foo").unwrap_err(); |
314 | 1 | } |
315 | | } |
316 | | |
317 | | #[cfg(test)] |
318 | | mod four_dp { |
319 | 1 | #[test] |
320 | 1 | fn ok() { |
321 | 1 | let value = super::four_dp("1").unwrap().1; |
322 | 1 | assert_eq!(value, 10000); |
323 | 1 | } |
324 | | |
325 | 1 | #[test] |
326 | 1 | fn ok_one_sig_fig() { |
327 | 1 | let value = super::four_dp("1.1").unwrap().1; |
328 | 1 | assert_eq!(value, 11000); |
329 | 1 | } |
330 | | |
331 | 1 | #[test] |
332 | 1 | fn ok_four_sig_fig() { |
333 | 1 | let value = super::four_dp("1.1111").unwrap().1; |
334 | 1 | assert_eq!(value, 11111); |
335 | 1 | } |
336 | | |
337 | 1 | #[test] |
338 | 1 | fn err_runthrough() { |
339 | 1 | super::four_dp("").unwrap_err(); |
340 | 1 | super::four_dp("1.").unwrap_err(); |
341 | 1 | } |
342 | | } |
343 | | |
344 | | #[cfg(test)] |
345 | | mod transaction { |
346 | | |
347 | 1 | #[test] |
348 | 1 | fn debug() { |
349 | 1 | assert_eq!( |
350 | 1 | format!("{:?}", super::Transaction::Deposit(1, 1, 2)), |
351 | 1 | "Deposit(1, 1, 2)" |
352 | 1 | ); |
353 | 1 | assert_eq!( |
354 | 1 | format!("{:?}", super::Transaction::Withdrawal(1, 1, 2)), |
355 | 1 | "Withdrawal(1, 1, 2)" |
356 | 1 | ); |
357 | 1 | assert_eq!( |
358 | 1 | format!("{:?}", super::Transaction::Dispute(1, 1)), |
359 | 1 | "Dispute(1, 1)" |
360 | 1 | ); |
361 | 1 | assert_eq!( |
362 | 1 | format!("{:?}", super::Transaction::Resolve(1, 1)), |
363 | 1 | "Resolve(1, 1)" |
364 | 1 | ); |
365 | 1 | assert_eq!( |
366 | 1 | format!("{:?}", super::Transaction::Chargeback(1, 1)), |
367 | 1 | "Chargeback(1, 1)" |
368 | 1 | ); |
369 | 1 | } |
370 | | |
371 | 1 | #[test] |
372 | 1 | fn partial_eq() { |
373 | 1 | assert_eq!( |
374 | 1 | super::Transaction::Deposit(1, 1, 20), |
375 | 1 | super::Transaction::Deposit(1, 1, 20) |
376 | 1 | ); |
377 | 1 | assert_eq!( |
378 | 1 | super::Transaction::Withdrawal(1, 1, 20), |
379 | 1 | super::Transaction::Withdrawal(1, 1, 20) |
380 | 1 | ); |
381 | 1 | assert_eq!( |
382 | 1 | super::Transaction::Dispute(1, 1), |
383 | 1 | super::Transaction::Dispute(1, 1) |
384 | 1 | ); |
385 | 1 | assert_eq!( |
386 | 1 | super::Transaction::Resolve(1, 1), |
387 | 1 | super::Transaction::Resolve(1, 1) |
388 | 1 | ); |
389 | 1 | assert_eq!( |
390 | 1 | super::Transaction::Chargeback(1, 1), |
391 | 1 | super::Transaction::Chargeback(1, 1) |
392 | 1 | ); |
393 | 1 | } |
394 | | } |
395 | | |
396 | | #[cfg(test)] |
397 | | mod ws { |
398 | | use super::*; |
399 | | use nom::bytes::complete::tag; |
400 | | |
401 | 1 | #[test] |
402 | 1 | fn ok_ws() { |
403 | 1 | let (input, tag) = ws(tag::<_, _, ()>("hello"))(" hello ").unwrap(); |
404 | 1 | |
405 | 1 | assert_eq!(input, ""); |
406 | 1 | assert_eq!(tag, "hello"); |
407 | 1 | } |
408 | | |
409 | 1 | #[test] |
410 | 1 | fn invalid_inner<'a>() { |
411 | 1 | ws(tag("hello"))("").unwrap_err() as nom::Err<(&'a str, nom::error::ErrorKind)>; |
412 | 1 | } |
413 | | } |
414 | | |
415 | | #[cfg(test)] |
416 | | mod parse_header { |
417 | | use crate::parse::parse_header; |
418 | | |
419 | 1 | #[test] |
420 | 1 | fn ok_no_white_space() { |
421 | 1 | parse_header("type,client,tx,amount").expect("Error whilst parsing header."); |
422 | 1 | } |
423 | | |
424 | 1 | #[test] |
425 | 1 | fn ok_with_white_space() { |
426 | 1 | parse_header(" type , client, tx , amount ") |
427 | 1 | .expect("Error whilst parsing header."); |
428 | 1 | } |
429 | | |
430 | 1 | #[test] |
431 | 1 | fn err_invalid_input() { |
432 | 1 | parse_header("client,type,ammount,tx").unwrap_err(); |
433 | 1 | } |
434 | | |
435 | 1 | #[test] |
436 | 1 | fn err_missing_value() { |
437 | 1 | parse_header("type,client,tx,").unwrap_err(); |
438 | 1 | } |
439 | | |
440 | 1 | #[test] |
441 | 1 | fn err_parser_runthrough() { |
442 | 1 | parse_header("x").unwrap_err(); |
443 | 1 | parse_header("type,x").unwrap_err(); |
444 | 1 | parse_header("type,client,x").unwrap_err(); |
445 | 1 | parse_header("type,client,tx,x").unwrap_err(); |
446 | 1 | } |
447 | | |
448 | 1 | #[test] |
449 | 1 | fn err_extra_value() { |
450 | 1 | parse_header("type,client,tx,amount,foo").unwrap_err(); |
451 | 1 | } |
452 | | } |