/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 | 13 | #[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 | 13 | #[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 | 165 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( |
65 | 165 | inner: F, |
66 | 165 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> |
67 | 165 | where |
68 | 165 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, |
69 | 165 | { |
70 | 165 | delimited(multispace0, inner, multispace0) |
71 | 165 | } 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 | 31 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 31 | inner: F, | 66 | 31 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 31 | where | 68 | 31 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 31 | { | 70 | 31 | delimited(multispace0, inner, multispace0) | 71 | 31 | } |
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, ()>::{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::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 | 33 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 33 | inner: F, | 66 | 33 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 33 | where | 68 | 33 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 33 | { | 70 | 33 | delimited(multispace0, inner, multispace0) | 71 | 33 | } |
csv_ledger::parse::ws::<nom::character::complete::u32<&str, nom::error::Error<&str>>, u32, nom::error::Error<&str>> Line | Count | Source | 64 | 29 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( | 65 | 29 | inner: F, | 66 | 29 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> | 67 | 29 | where | 68 | 29 | F: FnMut(&'a str) -> IResult<&'a str, O, E>, | 69 | 29 | { | 70 | 29 | delimited(multispace0, inner, multispace0) | 71 | 29 | } |
|
72 | | |
73 | | /// Test if a character is a digit. |
74 | 104 | pub fn digit(chr: char) -> bool { |
75 | 104 | chr.is_ascii() && is_digit(chr as u8) |
76 | 104 | } |
77 | | |
78 | | /// Parse a i64 number from a string, optionally allowing a maximum number of digits to be specified. |
79 | 49 | pub fn double(input: &str, max: Option<usize>) -> IResult<&str, i64> { |
80 | 49 | let (input, num48 ) = match max { |
81 | 17 | Some(m) => take_while_m_n(1, m, digit)(input), |
82 | 32 | None => take_while(digit)(input), |
83 | 1 | }?; |
84 | | |
85 | | // Convert the string to i64 |
86 | | Ok(( |
87 | 48 | input, |
88 | 48 | num.parse::<i64>() |
89 | 48 | .map_err(|_| nom_err("Could not parse number as i64.")14 )?14 , |
90 | | )) |
91 | 49 | } |
92 | | |
93 | | #[inline] |
94 | | /// Parse an up to four decimal place number as an i64 by multiplying by 10000. |
95 | 32 | pub fn four_dp(input: &str) -> IResult<&str, i64> { |
96 | 32 | let (input, pre_dp18 ) = double(input, None)?14 ; |
97 | | |
98 | | // Optionally parse decimal places |
99 | 18 | 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.checked_ilog10().unwrap_or(0))), |
106 | 16 | )); |
107 | 1 | } |
108 | 1 | |
109 | 1 | Ok((input, (pre_dp * 10000))) |
110 | 32 | } |
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 | 33 | pub fn parse_transaction(input: &str) -> Result<Transaction, NomErr<SubErr<&str>>> { |
135 | | // Parse the type of Transaction |
136 | 33 | let (input, key31 ) = terminated( |
137 | 33 | ws(alt(( |
138 | 33 | tag("deposit"), |
139 | 33 | tag("withdrawal"), |
140 | 33 | tag("dispute"), |
141 | 33 | tag("resolve"), |
142 | 33 | tag("chargeback"), |
143 | 33 | ))), |
144 | 33 | tag(","), |
145 | 33 | )(input)?2 ; |
146 | | |
147 | | // Parse the account and Transaction ID |
148 | 31 | let (input, client29 ) = terminated(ws(u16), tag(","))(input)?2 ; |
149 | 29 | let (input, tx27 ) = terminated(ws(u32), tag(","))(input)?2 ; |
150 | | |
151 | | // Parse the Transaction amount |
152 | 27 | let amount = delimited(multispace0, four_dp, multispace0)(input).ok(); |
153 | | |
154 | | // Check that the line has been consumed completely |
155 | 27 | if let Some((input14 ,_)) = amount && !input.is_empty()14 { |
156 | 1 | Err(nom_err("Input was not empty after parsing transaction."))?; |
157 | 26 | } |
158 | | |
159 | | // Convert result into Transaction |
160 | 26 | Ok(match (key, amount) { |
161 | 26 | ("deposit", Some((_, value))) => Transaction::Deposit(client, tx, value)10 , |
162 | 16 | ("withdrawal", Some((_, value))) => Transaction::Withdrawal(client, tx, value)2 , |
163 | 14 | ("dispute", None) => Transaction::Dispute(client, tx)4 , |
164 | 10 | ("resolve", None) => Transaction::Resolve(client, tx)2 , |
165 | 8 | ("chargeback", None) => Transaction::Chargeback(client, tx)2 , |
166 | 6 | (_, _) => Err(nom_err(if key == "deposit" || key == "withdrawal"2 { |
167 | 5 | "Deposit or Withdrawal with a missing or invalid amount." |
168 | | } else { |
169 | 1 | "Dispute, Resolve or Chargeback with an amount." |
170 | 6 | }))?, |
171 | | }) |
172 | 33 | } |
173 | | |
174 | | /// Parse the CSV header to validate that the CSV is in the correct format. |
175 | | /// Please note that whitespace will be ignored. |
176 | | /// |
177 | | /// Example: |
178 | | /// ```rs |
179 | | /// assert!(parse_header("type, client, tx, amount").is_ok()); |
180 | | /// assert!(parse_header(" type, client, tx ,amount ").is_ok()); |
181 | | /// |
182 | | /// assert!(parse_header("type, client, tx").is_err()); |
183 | | /// ``` |
184 | | #[inline] |
185 | 24 | pub fn parse_header(input: &str) -> Result<(), NomErr<SubErr<&str>>> { |
186 | 24 | let (input17 , _) = terminated(ws(tag("type")), tag(","))(input)?7 ; |
187 | 17 | let (input15 , _) = terminated(ws(tag("client")), tag(","))(input)?2 ; |
188 | 15 | let (input14 , _) = terminated(ws(tag("tx")), tag(","))(input)?1 ; |
189 | 14 | let (input12 , _) = ws(tag("amount"))(input)?2 ; |
190 | | |
191 | 12 | if !input.is_empty() { |
192 | 1 | return Err(nom_err("Input was not empty after parsing transaction.")); |
193 | 11 | } |
194 | 11 | |
195 | 11 | Ok(()) |
196 | 24 | } |
197 | | |
198 | | #[cfg(test)] |
199 | | mod parse_transaction { |
200 | | use crate::parse::{parse_transaction, Transaction}; |
201 | | |
202 | 1 | #[test] |
203 | 1 | fn deposit() { |
204 | 1 | let res = parse_transaction("deposit, 1, 2, 3.1").unwrap(); |
205 | 1 | assert_eq!(res, Transaction::Deposit(1, 2, 31000)); |
206 | 1 | } |
207 | | |
208 | 1 | #[test] |
209 | 1 | fn withdrawal() { |
210 | 1 | let res = parse_transaction("withdrawal, 1, 2, 3.0").unwrap(); |
211 | 1 | assert_eq!(res, Transaction::Withdrawal(1, 2, 30000)); |
212 | 1 | } |
213 | | |
214 | 1 | #[test] |
215 | 1 | fn dispute() { |
216 | 1 | let res = parse_transaction("dispute, 1, 2,").unwrap(); |
217 | 1 | assert_eq!(res, Transaction::Dispute(1, 2)); |
218 | 1 | } |
219 | | |
220 | 1 | #[test] |
221 | 1 | fn resolve() { |
222 | 1 | let res = parse_transaction("resolve, 1, 2,").unwrap(); |
223 | 1 | assert_eq!(res, Transaction::Resolve(1, 2)); |
224 | 1 | } |
225 | | |
226 | 1 | #[test] |
227 | 1 | fn chargeback() { |
228 | 1 | let res = parse_transaction("chargeback, 1, 2,").unwrap(); |
229 | 1 | assert_eq!(res, Transaction::Chargeback(1, 2)); |
230 | 1 | } |
231 | | |
232 | 1 | #[test] |
233 | 1 | fn ok_no_white_space() { |
234 | 1 | let res = parse_transaction("deposit,1,2,3.0").unwrap(); |
235 | 1 | |
236 | 1 | assert_eq!(res, Transaction::Deposit(1, 2, 30000)); |
237 | 1 | } |
238 | | |
239 | 1 | #[test] |
240 | 1 | fn ok_with_white_space() { |
241 | 1 | let res = parse_transaction(" deposit ,1 , 2, 3.0 ").unwrap(); |
242 | 1 | assert_eq!(res, Transaction::Deposit(1, 2, 30000)); |
243 | 1 | } |
244 | | |
245 | 1 | #[test] |
246 | 1 | fn ok_no_amount() { |
247 | 1 | let res = parse_transaction("dispute,1,2,").unwrap(); |
248 | 1 | assert_eq!(res, Transaction::Dispute(1, 2)); |
249 | 1 | } |
250 | | |
251 | 1 | #[test] |
252 | 1 | fn err_parser_runthrough() { |
253 | 1 | parse_transaction("x").unwrap_err(); |
254 | 1 | parse_transaction("deposit,x").unwrap_err(); |
255 | 1 | parse_transaction("deposit,1,x").unwrap_err(); |
256 | 1 | parse_transaction("deposit,1,2,x").unwrap_err(); |
257 | 1 | parse_transaction(&format!("deposit,1,2,2{}", f32::MAX)).unwrap_err(); |
258 | 1 | } |
259 | | |
260 | 1 | #[test] |
261 | 1 | fn err_invalid_u16() { |
262 | 1 | parse_transaction("deposit,65536,2,3.0").unwrap_err(); |
263 | 1 | } |
264 | | |
265 | 1 | #[test] |
266 | 1 | fn err_invalid_deposit() { |
267 | 1 | parse_transaction("deposit,1,2,").unwrap_err(); |
268 | 1 | } |
269 | | |
270 | 1 | #[test] |
271 | 1 | fn err_dispute_missing_value() { |
272 | 1 | parse_transaction("dispute,1,").unwrap_err(); |
273 | 1 | } |
274 | | |
275 | 1 | #[test] |
276 | 1 | fn err_withdrawal_missing_value() { |
277 | 1 | let res = parse_transaction("withdrawal,1,2,").unwrap_err(); |
278 | 1 | assert_eq!( |
279 | 1 | res.to_string(), |
280 | 1 | "Parsing Failure: Error { input: \"Deposit or Withdrawal with a missing or invalid amount.\", code: Fail }" |
281 | 1 | ); |
282 | 1 | } |
283 | | |
284 | 1 | #[test] |
285 | 1 | fn err_deposit_missing_value() { |
286 | 1 | let res = parse_transaction("deposit,1,2,").unwrap_err(); |
287 | 1 | assert_eq!( |
288 | 1 | res.to_string(), |
289 | 1 | "Parsing Failure: Error { input: \"Deposit or Withdrawal with a missing or invalid amount.\", code: Fail }" |
290 | 1 | ); |
291 | 1 | } |
292 | | |
293 | 1 | #[test] |
294 | 1 | fn err_dispute_extra_value() { |
295 | 1 | let res = parse_transaction("dispute,1,2,3.0").unwrap_err(); |
296 | 1 | |
297 | 1 | assert_eq!( |
298 | 1 | res.to_string(), |
299 | 1 | "Parsing Failure: Error { input: \"Dispute, Resolve or Chargeback with an amount.\", code: Fail }" |
300 | 1 | ); |
301 | 1 | } |
302 | | |
303 | 1 | #[test] |
304 | 1 | fn err_extra_value() { |
305 | 1 | parse_transaction("withdrawal,1,2,3.0,foo").unwrap_err(); |
306 | 1 | } |
307 | | } |
308 | | |
309 | | #[cfg(test)] |
310 | | mod four_dp { |
311 | 1 | #[test] |
312 | 1 | fn ok() { |
313 | 1 | let value = super::four_dp("1").unwrap().1; |
314 | 1 | assert_eq!(value, 10000); |
315 | 1 | } |
316 | | |
317 | 1 | #[test] |
318 | 1 | fn ok_one_sig_fig() { |
319 | 1 | let value = super::four_dp("1.1").unwrap().1; |
320 | 1 | assert_eq!(value, 11000); |
321 | 1 | } |
322 | | |
323 | 1 | #[test] |
324 | 1 | fn ok_four_sig_fig() { |
325 | 1 | let value = super::four_dp("1.1111").unwrap().1; |
326 | 1 | assert_eq!(value, 11111); |
327 | 1 | } |
328 | | |
329 | 1 | #[test] |
330 | 1 | fn err_runthrough() { |
331 | 1 | super::four_dp("").unwrap_err(); |
332 | 1 | super::four_dp("1.").unwrap_err(); |
333 | 1 | } |
334 | | } |
335 | | |
336 | | #[cfg(test)] |
337 | | mod transaction { |
338 | | |
339 | 1 | #[test] |
340 | 1 | fn debug() { |
341 | 1 | assert_eq!( |
342 | 1 | format!("{:?}", super::Transaction::Deposit(1, 1, 2)), |
343 | 1 | "Deposit(1, 1, 2)" |
344 | 1 | ); |
345 | 1 | assert_eq!( |
346 | 1 | format!("{:?}", super::Transaction::Withdrawal(1, 1, 2)), |
347 | 1 | "Withdrawal(1, 1, 2)" |
348 | 1 | ); |
349 | 1 | assert_eq!( |
350 | 1 | format!("{:?}", super::Transaction::Dispute(1, 1)), |
351 | 1 | "Dispute(1, 1)" |
352 | 1 | ); |
353 | 1 | assert_eq!( |
354 | 1 | format!("{:?}", super::Transaction::Resolve(1, 1)), |
355 | 1 | "Resolve(1, 1)" |
356 | 1 | ); |
357 | 1 | assert_eq!( |
358 | 1 | format!("{:?}", super::Transaction::Chargeback(1, 1)), |
359 | 1 | "Chargeback(1, 1)" |
360 | 1 | ); |
361 | 1 | } |
362 | | |
363 | 1 | #[test] |
364 | 1 | fn partial_eq() { |
365 | 1 | assert_eq!( |
366 | 1 | super::Transaction::Deposit(1, 1, 20), |
367 | 1 | super::Transaction::Deposit(1, 1, 20) |
368 | 1 | ); |
369 | 1 | assert_eq!( |
370 | 1 | super::Transaction::Withdrawal(1, 1, 20), |
371 | 1 | super::Transaction::Withdrawal(1, 1, 20) |
372 | 1 | ); |
373 | 1 | assert_eq!( |
374 | 1 | super::Transaction::Dispute(1, 1), |
375 | 1 | super::Transaction::Dispute(1, 1) |
376 | 1 | ); |
377 | 1 | assert_eq!( |
378 | 1 | super::Transaction::Resolve(1, 1), |
379 | 1 | super::Transaction::Resolve(1, 1) |
380 | 1 | ); |
381 | 1 | assert_eq!( |
382 | 1 | super::Transaction::Chargeback(1, 1), |
383 | 1 | super::Transaction::Chargeback(1, 1) |
384 | 1 | ); |
385 | 1 | } |
386 | | } |
387 | | |
388 | | #[cfg(test)] |
389 | | mod ws { |
390 | | use super::*; |
391 | | use nom::bytes::complete::tag; |
392 | | |
393 | 1 | #[test] |
394 | 1 | fn ok_ws() { |
395 | 1 | let (input, tag) = ws(tag::<_, _, ()>("hello"))(" hello ").unwrap(); |
396 | 1 | |
397 | 1 | assert_eq!(input, ""); |
398 | 1 | assert_eq!(tag, "hello"); |
399 | 1 | } |
400 | | |
401 | 1 | #[test] |
402 | 1 | fn invalid_inner<'a>() { |
403 | 1 | ws(tag("hello"))("").unwrap_err() as nom::Err<(&'a str, nom::error::ErrorKind)>; |
404 | 1 | } |
405 | | } |
406 | | |
407 | | #[cfg(test)] |
408 | | mod parse_header { |
409 | | use crate::parse::parse_header; |
410 | | |
411 | 1 | #[test] |
412 | 1 | fn ok_no_white_space() { |
413 | 1 | parse_header("type,client,tx,amount").expect("Error whilst parsing header."); |
414 | 1 | } |
415 | | |
416 | 1 | #[test] |
417 | 1 | fn ok_with_white_space() { |
418 | 1 | parse_header(" type , client, tx , amount ") |
419 | 1 | .expect("Error whilst parsing header."); |
420 | 1 | } |
421 | | |
422 | 1 | #[test] |
423 | 1 | fn err_invalid_input() { |
424 | 1 | parse_header("client,type,ammount,tx").unwrap_err(); |
425 | 1 | } |
426 | | |
427 | 1 | #[test] |
428 | 1 | fn err_missing_value() { |
429 | 1 | parse_header("type,client,tx,").unwrap_err(); |
430 | 1 | } |
431 | | |
432 | 1 | #[test] |
433 | 1 | fn err_parser_runthrough() { |
434 | 1 | parse_header("x").unwrap_err(); |
435 | 1 | parse_header("type,x").unwrap_err(); |
436 | 1 | parse_header("type,client,x").unwrap_err(); |
437 | 1 | parse_header("type,client,tx,x").unwrap_err(); |
438 | 1 | } |
439 | | |
440 | 1 | #[test] |
441 | 1 | fn err_extra_value() { |
442 | 1 | parse_header("type,client,tx,amount,foo").unwrap_err(); |
443 | 1 | } |
444 | | } |