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
22mod error;
23mod parser;
24mod serializer;
25
26pub use error::{CsvError, CsvErrorKind};
27pub use parser::CsvParser;
28pub use serializer::{CsvSerializeError, CsvSerializer, to_string, to_vec, to_writer};
29
30// Re-export DeserializeError for convenience
31pub use facet_format::DeserializeError;
32
33/// Deserialize a value from a CSV string into an owned type.
34///
35/// Note: This parses a single CSV row (not including the header).
36/// For multiple rows, iterate over lines and call this for each.
37///
38/// # Example
39///
40/// ```
41/// use facet::Facet;
42/// use facet_csv::from_str;
43///
44/// #[derive(Facet, Debug, PartialEq)]
45/// struct Person {
46/// name: String,
47/// age: u32,
48/// }
49///
50/// let csv = "Alice,30";
51/// let person: Person = from_str(csv).unwrap();
52/// assert_eq!(person.name, "Alice");
53/// assert_eq!(person.age, 30);
54/// ```
55pub fn from_str<T>(input: &str) -> Result<T, DeserializeError>
56where
57 T: facet_core::Facet<'static>,
58{
59 use facet_format::FormatDeserializer;
60 let mut parser = CsvParser::new(input);
61 let mut de = FormatDeserializer::new_owned(&mut parser);
62 de.deserialize_root()
63}
64
65/// Deserialize a value from a CSV string, allowing zero-copy borrowing.
66///
67/// # Example
68///
69/// ```
70/// use facet::Facet;
71/// use facet_csv::from_str_borrowed;
72///
73/// #[derive(Facet, Debug, PartialEq)]
74/// struct Person {
75/// name: String,
76/// age: u32,
77/// }
78///
79/// let csv = "Alice,30";
80/// let person: Person = from_str_borrowed(csv).unwrap();
81/// assert_eq!(person.name, "Alice");
82/// assert_eq!(person.age, 30);
83/// ```
84pub fn from_str_borrowed<'input, 'facet, T>(input: &'input str) -> Result<T, DeserializeError>
85where
86 T: facet_core::Facet<'facet>,
87 'input: 'facet,
88{
89 use facet_format::FormatDeserializer;
90 let mut parser = CsvParser::new(input);
91 let mut de = FormatDeserializer::new(&mut parser);
92 de.deserialize_root()
93}
94
95/// Deserialize a value from CSV bytes into an owned type.
96///
97/// # Errors
98///
99/// Returns an error if the input is not valid UTF-8 or if deserialization fails.
100///
101/// # Example
102///
103/// ```
104/// use facet::Facet;
105/// use facet_csv::from_slice;
106///
107/// #[derive(Facet, Debug, PartialEq)]
108/// struct Person {
109/// name: String,
110/// age: u32,
111/// }
112///
113/// let csv = b"Alice,30";
114/// let person: Person = from_slice(csv).unwrap();
115/// assert_eq!(person.name, "Alice");
116/// assert_eq!(person.age, 30);
117/// ```
118pub fn from_slice<T>(input: &[u8]) -> Result<T, DeserializeError>
119where
120 T: facet_core::Facet<'static>,
121{
122 let s = core::str::from_utf8(input).map_err(|e| {
123 let mut context = [0u8; 16];
124 let context_len = e.valid_up_to().min(16);
125 context[..context_len].copy_from_slice(&input[..context_len]);
126 facet_format::DeserializeErrorKind::InvalidUtf8 {
127 context,
128 context_len: context_len as u8,
129 }
130 .with_span(facet_reflect::Span::new(e.valid_up_to(), 1))
131 })?;
132 from_str(s)
133}
134
135/// Deserialize a value from CSV bytes, allowing zero-copy borrowing.
136///
137/// # Errors
138///
139/// Returns an error if the input is not valid UTF-8 or if deserialization fails.
140///
141/// # Example
142///
143/// ```
144/// use facet::Facet;
145/// use facet_csv::from_slice_borrowed;
146///
147/// #[derive(Facet, Debug, PartialEq)]
148/// struct Person {
149/// name: String,
150/// age: u32,
151/// }
152///
153/// let csv = b"Alice,30";
154/// let person: Person = from_slice_borrowed(csv).unwrap();
155/// assert_eq!(person.name, "Alice");
156/// assert_eq!(person.age, 30);
157/// ```
158pub fn from_slice_borrowed<'input, 'facet, T>(input: &'input [u8]) -> Result<T, DeserializeError>
159where
160 T: facet_core::Facet<'facet>,
161 'input: 'facet,
162{
163 let s = core::str::from_utf8(input).map_err(|e| {
164 let mut context = [0u8; 16];
165 let context_len = e.valid_up_to().min(16);
166 context[..context_len].copy_from_slice(&input[..context_len]);
167 facet_format::DeserializeErrorKind::InvalidUtf8 {
168 context,
169 context_len: context_len as u8,
170 }
171 .with_span(facet_reflect::Span::new(e.valid_up_to(), 1))
172 })?;
173 from_str_borrowed(s)
174}