csv_line/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3
4use csv::StringRecord;
5use parse::CsvRow;
6use serde::de::DeserializeOwned;
7
8mod parse;
9
10/// A parser for a single line of CSV data.
11pub struct CSVLine {
12    separator: char,
13}
14
15impl CSVLine {
16    /// Creates a new parser with the default separator (',').
17    pub fn new() -> Self {
18        Default::default()
19    }
20
21    /// Sets the separator character for the parser.
22    pub fn with_separator(mut self, separator: char) -> Self {
23        self.separator = separator;
24        self
25    }
26
27    /// Deserializes a string into a custom type.
28    pub fn decode_str<T: DeserializeOwned>(&self, s: &str) -> Result<T, csv::Error> {
29        let record = StringRecord::from_iter(CsvRow::new(s, self.separator));
30        record.deserialize(None)
31    }
32}
33
34impl Default for CSVLine {
35    fn default() -> Self {
36        Self { separator: ',' }
37    }
38}
39
40/// Deserializes a string into a custom type.
41pub fn from_str<T: DeserializeOwned>(s: &str) -> Result<T, csv::Error> {
42    CSVLine::new().decode_str(s)
43}
44
45/// Deserializes a string with a custom separator.
46///
47/// # Arguments
48///
49/// * `s` - The string slice to deserialize.
50/// * `sep` - The separator character.
51///
52/// # Example
53///
54/// ```
55/// #[derive(Debug, PartialEq, serde::Deserialize)]
56/// struct Bar(Vec<u32>);
57///
58/// assert_eq!(csv_line::from_str_sep::<Bar>("31 42 28 97 0", ' ').unwrap(), Bar(vec![31,42,28,97,0]));
59/// ```
60pub fn from_str_sep<T: DeserializeOwned>(s: &str, sep: char) -> Result<T, csv::Error> {
61    CSVLine::new().with_separator(sep).decode_str(s)
62}
63
64#[cfg(test)]
65mod tests {
66    use serde::Deserialize;
67
68    use super::*;
69
70    #[test]
71    fn basic() {
72        #[derive(Debug, PartialEq, Deserialize)]
73        struct Foo(String);
74        assert_eq!(from_str::<Foo>("foo").unwrap(), Foo("foo".into()));
75        assert_eq!(from_str_sep::<Foo>("foo", ' ').unwrap(), Foo("foo".into()));
76    }
77
78    #[test]
79    fn empty() {
80        #[derive(Debug, PartialEq, Deserialize)]
81        struct Foo(Option<String>);
82        assert_eq!(from_str::<Foo>("").unwrap(), Foo(None));
83        assert_eq!(from_str_sep::<Foo>("", ' ').unwrap(), Foo(None));
84    }
85
86    #[test]
87    fn types() {
88        #[derive(Debug, PartialEq, Deserialize)]
89        struct Foo {
90            text: String,
91            maybe_text: Option<String>,
92            num: i32,
93            flag: bool,
94        }
95        assert_eq!(
96            from_str::<Foo>(r#""foo,bar",,1,true"#).unwrap(),
97            Foo {
98                text: "foo,bar".into(),
99                maybe_text: None,
100                num: 1,
101                flag: true
102            }
103        );
104        assert_eq!(
105            from_str_sep::<Foo>(r#""foo bar"  1 true"#, ' ').unwrap(),
106            Foo {
107                text: "foo bar".into(),
108                maybe_text: None,
109                num: 1,
110                flag: true
111            }
112        );
113    }
114
115    #[test]
116    fn tsv() {
117        #[derive(Debug, PartialEq, Deserialize)]
118        struct Foo(String, String);
119        assert_eq!(
120            CSVLine::new()
121                .with_separator('\t')
122                .decode_str::<Foo>("foo\tbar")
123                .unwrap(),
124            Foo("foo".into(), "bar".into())
125        );
126    }
127}