Skip to main content

csv/
writer.rs

1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5
6use crate::error::WriteError;
7
8/// Custom write abstraction that allows `Writer` to work without `std::io::Write`.
9///
10/// This trait is automatically implemented for `Vec<u8>` and, when the `std`
11/// feature is enabled, for any `std::io::Write` type.
12pub trait Write {
13    /// Write all bytes in `buf` to the sink.
14    ///
15    /// Returns `Err` if the write could not be completed.
16    fn write(&mut self, buf: &[u8]) -> Result<(), WriteError>;
17    /// Flush any buffered data to the underlying sink.
18    fn flush(&mut self) -> Result<(), WriteError>;
19}
20
21#[cfg(not(feature = "std"))]
22impl Write for Vec<u8> {
23    fn write(&mut self, buf: &[u8]) -> Result<(), WriteError> {
24        self.extend_from_slice(buf);
25        Ok(())
26    }
27    fn flush(&mut self) -> Result<(), WriteError> {
28        Ok(())
29    }
30}
31
32#[cfg(feature = "std")]
33impl<W: std::io::Write> Write for W {
34    fn write(&mut self, buf: &[u8]) -> Result<(), WriteError> {
35        self.write_all(buf)?;
36        Ok(())
37    }
38    fn flush(&mut self) -> Result<(), WriteError> {
39        self.flush()?;
40        Ok(())
41    }
42}
43
44/// Writes CSV data to a [`Write`] sink.
45///
46/// Fields containing the delimiter, a newline, or a double-quote are
47/// automatically quoted. `""` escape sequences are used for quotes
48/// within quoted fields.
49///
50/// Internal buffering is used to avoid many small writes. Callers should
51/// not wrap the writer in a `BufWriter`.
52///
53/// # Example
54///
55/// ```no_run
56/// use csv::Writer;
57///
58/// let mut w = Writer::new(Vec::new());
59/// w.write_row(["name", "age", "city"])?;
60/// w.write_row(["Alice", "30", "New York, NY"])?;
61/// let result = String::from_utf8(w.into_inner()?).unwrap();
62/// assert_eq!(result, "name,age,city\r\nAlice,30,\"New York, NY\"\r\n");
63/// # Ok::<_, csv::WriteError>(())
64/// ```
65pub struct Writer<W: Write> {
66    pub(crate) writer: Option<W>,
67    pub(crate) delimiter: u8,
68    pub(crate) flexible: bool,
69    pub(crate) num_fields: Option<usize>,
70    pub(crate) row_count: usize,
71    pub(crate) buf: Vec<u8>,
72    pub(crate) headers: Option<Vec<String>>,
73    pub(crate) headers_written: bool,
74    #[cfg(feature = "serde")]
75    pub(crate) ser_values: Vec<Option<Vec<u8>>>,
76    #[cfg(feature = "serde")]
77    pub(crate) ser_capture_buf: Vec<u8>,
78}
79
80impl<W: Write> Writer<W> {
81    /// Create a new writer wrapping the given output sink.
82    ///
83    /// The writer starts with default comma delimiter and strict
84    /// field-count validation (all rows must have the same number of fields).
85    pub fn new(writer: W) -> Self {
86        Writer {
87            writer: Some(writer),
88            delimiter: b',',
89            flexible: false,
90            num_fields: None,
91            row_count: 0,
92            buf: Vec::with_capacity(8192),
93            headers: None,
94            headers_written: false,
95            #[cfg(feature = "serde")]
96            ser_values: Vec::new(),
97            #[cfg(feature = "serde")]
98            ser_capture_buf: Vec::new(),
99        }
100    }
101
102    /// Set the field delimiter byte (default is `,`).
103    ///
104    /// ```no_run
105    /// use csv::Writer;
106    /// let mut w = Writer::new(Vec::new()).set_delimiter(b'\t');
107    /// w.write_row(["a", "b", "c"])?;
108    /// # Ok::<_, csv::WriteError>(())
109    /// ```
110    pub fn set_delimiter(mut self, byte: u8) -> Self {
111        self.delimiter = byte;
112        self
113    }
114
115    /// Set whether variable field counts are allowed (default is `false`).
116    ///
117    /// When `false` (strict), all rows must have the same number of fields.
118    /// When `true`, rows may vary in field count.
119    pub fn set_flexible(mut self, yes: bool) -> Self {
120        self.flexible = yes;
121        self
122    }
123
124    /// Store column names for schema awareness.
125    ///
126    /// This sets the expected header names for future serde support and strict
127    /// field-count validation. It does **not** write anything to the output.
128    /// Use [`write_headers`](Self::write_headers) to emit the header row.
129    pub fn set_headers(mut self, headers: Vec<String>) -> Self {
130        self.headers = Some(headers);
131        self
132    }
133
134    /// Returns the stored header names, if any.
135    pub fn headers(&self) -> Option<&[String]> {
136        self.headers.as_deref()
137    }
138
139    /// Write a header row to the output and store the column names internally.
140    ///
141    /// The header row is written as a single CSV line. The field count from
142    /// this row becomes the expected count for all subsequent rows (unless
143    /// flexible mode is enabled).
144    ///
145    /// If this method has already been called, subsequent calls return
146    /// [`WriteError::HeadersAlreadyWritten`].
147    ///
148    /// # Errors
149    ///
150    /// Returns [`WriteError::HeadersAlreadyWritten`] if headers have already
151    /// been written. Returns [`WriteError::InconsistentFieldCount`] if the
152    /// number of headers differs from a prior [`write_row`](Self::write_row)
153    /// or from headers stored via [`set_headers`](Self::set_headers).
154    /// Returns [`WriteError::Io`] if the underlying writer fails.
155    pub fn write_headers<I, T>(&mut self, headers: I) -> Result<(), WriteError>
156    where
157        I: IntoIterator<Item = T>,
158        T: AsRef<str>,
159    {
160        if self.headers_written {
161            return Err(WriteError::HeadersAlreadyWritten);
162        }
163
164        let strings: Vec<String> = headers.into_iter().map(|s| s.as_ref().to_string()).collect();
165        let count = strings.len();
166
167        for (i, s) in strings.iter().enumerate() {
168            if i > 0 {
169                self.buf.push(self.delimiter);
170            }
171            self.write_field(s.as_bytes())?;
172        }
173
174        self.buf.extend_from_slice(b"\r\n");
175        self.row_count += 1;
176        self.headers = Some(strings);
177        self.headers_written = true;
178        self.num_fields = Some(count);
179
180        if self.buf.len() >= 8192 {
181            self.flush()?;
182        }
183
184        Ok(())
185    }
186
187    /// Write a single row.
188    ///
189    /// Each element in the iterator is written as a CSV field. Fields are
190    /// auto-quoted if they contain the delimiter, a newline (`\n` or `\r`),
191    /// or a double-quote (`"`).
192    ///
193    /// # Errors
194    ///
195    /// Returns [`WriteError::InconsistentFieldCount`] if the number of
196    /// fields differs from previous rows (unless flexible mode is enabled).
197    /// Returns [`WriteError::Io`] if the underlying writer fails.
198    pub fn write_row<I, T>(&mut self, row: I) -> Result<(), WriteError>
199    where
200        I: IntoIterator<Item = T>,
201        T: AsRef<[u8]>,
202    {
203        if self.flexible {
204            let mut first = true;
205            let mut field_count = 0;
206            for field in row {
207                if !first {
208                    self.buf.push(self.delimiter);
209                }
210                first = false;
211                self.write_field(field.as_ref())?;
212                field_count += 1;
213            }
214            if self.num_fields.is_none() && field_count > 0 {
215                self.num_fields = Some(field_count);
216            }
217            self.buf.extend_from_slice(b"\r\n");
218            self.row_count += 1;
219        } else {
220            let buf_start = self.buf.len();
221            let mut field_count = 0;
222            for (i, field) in row.into_iter().enumerate() {
223                if i > 0 {
224                    self.buf.push(self.delimiter);
225                }
226                self.write_field(field.as_ref())?;
227                field_count += 1;
228            }
229
230            match self.num_fields {
231                Some(expected) if field_count != expected => {
232                    self.buf.truncate(buf_start);
233                    return Err(WriteError::InconsistentFieldCount {
234                        expected,
235                        found: field_count,
236                        row: self.row_count + 1,
237                    });
238                }
239                None => {
240                    self.num_fields = Some(field_count);
241                }
242                _ => {}
243            }
244
245            self.buf.extend_from_slice(b"\r\n");
246            self.row_count += 1;
247        }
248
249        if self.buf.len() >= 8192 {
250            self.flush()?;
251        }
252
253        Ok(())
254    }
255
256    fn write_field(&mut self, field: &[u8]) -> Result<(), WriteError> {
257        write_csv_field(&mut self.buf, self.delimiter, field);
258        Ok(())
259    }
260
261    /// Flush the internal buffer to the underlying writer.
262    pub fn flush(&mut self) -> Result<(), WriteError> {
263        if let Some(writer) = self.writer.as_mut() {
264            Write::write(writer, &self.buf)?;
265            Write::flush(writer)?;
266        }
267        self.buf.clear();
268        Ok(())
269    }
270
271    /// Flush any remaining data and return
272    /// the underlying writer.
273    pub fn into_inner(mut self) -> Result<W, WriteError> {
274        self.flush()?;
275        Ok(self.writer.take().unwrap())
276    }
277}
278
279impl<W: Write> Drop for Writer<W> {
280    fn drop(&mut self) {
281        if self.writer.is_some() {
282            let _ = self.flush();
283        }
284    }
285}
286
287/// Write a single CSV field to `buf`, applying quoting as needed.
288///
289/// Writes directly into `buf` to avoid intermediate buffering.
290pub(crate) fn write_csv_field(buf: &mut Vec<u8>, delimiter: u8, field: &[u8]) {
291    let needs_quoting = field.is_empty()
292        || memchr::memchr3(delimiter, b'\r', b'\n', field).is_some()
293        || memchr::memchr(b'"', field).is_some();
294
295    if !needs_quoting {
296        buf.extend_from_slice(field);
297        return;
298    }
299
300    buf.push(b'"');
301
302    let mut pos = 0;
303    while let Some(quote_offset) = memchr::memchr(b'"', &field[pos..]) {
304        let abs_pos = pos + quote_offset;
305        buf.extend_from_slice(&field[pos..abs_pos]);
306        buf.extend_from_slice(b"\"\"");
307        pos = abs_pos + 1;
308    }
309    buf.extend_from_slice(&field[pos..]);
310    buf.push(b'"');
311}