facet_csv/
lib.rs

1//! CSV parser and serializer using facet-format.
2//!
3//! **Note:** CSV is a fundamentally different format from JSON/XML/YAML.
4//! While those formats are tree-structured and map naturally to nested types,
5//! CSV is a flat, row-based format where each row represents a single record
6//! and each column represents a field.
7//!
8//! This crate provides basic CSV support via the `FormatParser` trait, but
9//! has significant limitations:
10//!
11//! - No support for nested structures (CSV is inherently flat)
12//! - No support for arrays/sequences as field values
13//! - No support for enums beyond unit variants (encoded as strings)
14//! - All values are strings and must be parseable to target types
15//!
16//! For more sophisticated CSV handling, consider a dedicated CSV library.
17
18#![forbid(unsafe_code)]
19
20extern crate alloc;
21
22use alloc::string::ToString;
23
24mod error;
25mod parser;
26mod serializer;
27
28pub use error::{CsvError, CsvErrorKind};
29pub use parser::CsvParser;
30pub use serializer::{CsvSerializeError, CsvSerializer, to_string, to_vec, to_writer};
31
32// Re-export DeserializeError for convenience
33pub use facet_format::DeserializeError;
34
35/// Deserialize a value from a CSV string into an owned type.
36///
37/// Note: This parses a single CSV row (not including the header).
38/// For multiple rows, iterate over lines and call this for each.
39///
40/// # Example
41///
42/// ```
43/// use facet::Facet;
44/// use facet_csv::from_str;
45///
46/// #[derive(Facet, Debug, PartialEq)]
47/// struct Person {
48///     name: String,
49///     age: u32,
50/// }
51///
52/// let csv = "Alice,30";
53/// let person: Person = from_str(csv).unwrap();
54/// assert_eq!(person.name, "Alice");
55/// assert_eq!(person.age, 30);
56/// ```
57pub fn from_str<T>(input: &str) -> Result<T, DeserializeError<CsvError>>
58where
59    T: facet_core::Facet<'static>,
60{
61    use facet_format::FormatDeserializer;
62    let parser = CsvParser::new(input);
63    let mut de = FormatDeserializer::new_owned(parser);
64    de.deserialize_root()
65}
66
67/// Deserialize a value from a CSV string, allowing zero-copy borrowing.
68///
69/// # Example
70///
71/// ```
72/// use facet::Facet;
73/// use facet_csv::from_str_borrowed;
74///
75/// #[derive(Facet, Debug, PartialEq)]
76/// struct Person {
77///     name: String,
78///     age: u32,
79/// }
80///
81/// let csv = "Alice,30";
82/// let person: Person = from_str_borrowed(csv).unwrap();
83/// assert_eq!(person.name, "Alice");
84/// assert_eq!(person.age, 30);
85/// ```
86pub fn from_str_borrowed<'input, 'facet, T>(
87    input: &'input str,
88) -> Result<T, DeserializeError<CsvError>>
89where
90    T: facet_core::Facet<'facet>,
91    'input: 'facet,
92{
93    use facet_format::FormatDeserializer;
94    let parser = CsvParser::new(input);
95    let mut de = FormatDeserializer::new(parser);
96    de.deserialize_root()
97}
98
99/// Deserialize a value from CSV bytes into an owned type.
100///
101/// # Errors
102///
103/// Returns an error if the input is not valid UTF-8 or if deserialization fails.
104///
105/// # Example
106///
107/// ```
108/// use facet::Facet;
109/// use facet_csv::from_slice;
110///
111/// #[derive(Facet, Debug, PartialEq)]
112/// struct Person {
113///     name: String,
114///     age: u32,
115/// }
116///
117/// let csv = b"Alice,30";
118/// let person: Person = from_slice(csv).unwrap();
119/// assert_eq!(person.name, "Alice");
120/// assert_eq!(person.age, 30);
121/// ```
122pub fn from_slice<T>(input: &[u8]) -> Result<T, DeserializeError<CsvError>>
123where
124    T: facet_core::Facet<'static>,
125{
126    let s = core::str::from_utf8(input).map_err(|e| {
127        DeserializeError::Parser(CsvError::new(CsvErrorKind::InvalidUtf8 {
128            message: e.to_string(),
129        }))
130    })?;
131    from_str(s)
132}
133
134/// Deserialize a value from CSV bytes, allowing zero-copy borrowing.
135///
136/// # Errors
137///
138/// Returns an error if the input is not valid UTF-8 or if deserialization fails.
139///
140/// # Example
141///
142/// ```
143/// use facet::Facet;
144/// use facet_csv::from_slice_borrowed;
145///
146/// #[derive(Facet, Debug, PartialEq)]
147/// struct Person {
148///     name: String,
149///     age: u32,
150/// }
151///
152/// let csv = b"Alice,30";
153/// let person: Person = from_slice_borrowed(csv).unwrap();
154/// assert_eq!(person.name, "Alice");
155/// assert_eq!(person.age, 30);
156/// ```
157pub fn from_slice_borrowed<'input, 'facet, T>(
158    input: &'input [u8],
159) -> Result<T, DeserializeError<CsvError>>
160where
161    T: facet_core::Facet<'facet>,
162    'input: 'facet,
163{
164    let s = core::str::from_utf8(input).map_err(|e| {
165        DeserializeError::Parser(CsvError::new(CsvErrorKind::InvalidUtf8 {
166            message: e.to_string(),
167        }))
168    })?;
169    from_str_borrowed(s)
170}