1use alloc::string::{String, ToString};
4
5use crate::error::CsvDecodeError;
6
7pub trait CsvField: Sized {
15 fn encode_field(&self) -> String;
17
18 fn decode_field(input: &str) -> Result<Self, CsvDecodeError>;
20}
21
22macro_rules! impl_int {
23 ($($t:ty),* $(,)?) => {$(
24 impl CsvField for $t {
25 fn encode_field(&self) -> String {
26 self.to_string()
27 }
28 fn decode_field(input: &str) -> Result<Self, CsvDecodeError> {
29 input.parse::<$t>().map_err(|_| {
30 CsvDecodeError::field("field is not an integer that fits the target type")
31 })
32 }
33 }
34 )*};
35}
36impl_int!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
37
38impl CsvField for bool {
39 fn encode_field(&self) -> String {
40 if *self { "true" } else { "false" }.to_string()
41 }
42 fn decode_field(input: &str) -> Result<Self, CsvDecodeError> {
43 match input {
44 "true" => Ok(true),
45 "false" => Ok(false),
46 _ => Err(CsvDecodeError::field("field is not `true` or `false`")),
47 }
48 }
49}
50
51impl CsvField for String {
52 fn encode_field(&self) -> String {
53 self.clone()
54 }
55 fn decode_field(input: &str) -> Result<Self, CsvDecodeError> {
56 Ok(input.to_string())
57 }
58}
59
60impl<T: CsvField> CsvField for Option<T> {
61 fn encode_field(&self) -> String {
62 match self {
63 Some(value) => value.encode_field(),
64 None => String::new(),
65 }
66 }
67 fn decode_field(input: &str) -> Result<Self, CsvDecodeError> {
68 if input.is_empty() {
69 Ok(None)
70 } else {
71 T::decode_field(input).map(Some)
72 }
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn integers_round_trip_and_reject() {
82 assert_eq!(255u8.encode_field(), "255");
83 assert_eq!(u8::decode_field("255").unwrap(), 255);
84 assert!(u8::decode_field("256").is_err());
85 assert!(u8::decode_field("").is_err());
86 assert_eq!(i32::decode_field("-5").unwrap(), -5);
87 }
88
89 #[test]
90 fn bool_is_strict() {
91 assert_eq!(true.encode_field(), "true");
92 assert_eq!(false.encode_field(), "false");
93 assert!(bool::decode_field("true").unwrap());
94 assert!(!bool::decode_field("false").unwrap());
95 assert!(bool::decode_field("True").is_err());
96 assert!(bool::decode_field("1").is_err());
97 }
98
99 #[test]
100 fn string_encode_and_decode() {
101 assert_eq!(String::from("hi").encode_field(), "hi");
102 assert_eq!(String::decode_field("hi").unwrap(), "hi");
103 assert_eq!(String::decode_field("").unwrap(), "");
104 }
105
106 #[test]
107 fn option_uses_empty_for_none() {
108 assert_eq!(Option::<u8>::None.encode_field(), "");
109 assert_eq!(Some(7u8).encode_field(), "7");
110 assert_eq!(Option::<u8>::decode_field("").unwrap(), None);
111 assert_eq!(Option::<u8>::decode_field("7").unwrap(), Some(7));
112 assert!(Option::<u8>::decode_field("x").is_err());
113 }
114}