Struct Writer

Source
pub struct Writer { /* private fields */ }
Expand description

A already configured CSV writer.

A CSV writer takes as input Rust values and writes those values in a valid CSV format as output.

While CSV writing is considerably easier than parsing CSV, a proper writer will do a number of things for you:

  1. Quote fields when necessary.
  2. Check that all records have the same number of fields.
  3. Write records with a single empty field correctly.
  4. Automatically serialize normal Rust types to CSV records. When that type is a struct, a header row is automatically written corresponding to the fields of that struct.
  5. Use buffering intelligently and otherwise avoid allocation. (This means that callers should not do their own buffering.)

All of the above can be configured using a WriterBuilder.

Note that the default configuration of a Writer uses \n for record terminators instead of \r\n as specified by RFC 4180. Use the terminator method on WriterBuilder to set the terminator to \r\n if it’s desired.

Implementations§

Source§

impl Writer

Source

pub fn serialize<S: Serialize>( &mut self, buf: &mut Vec<u8>, record: S, ) -> Result<()>

Serialize a single record using Serde.

§Example

This shows how to serialize normal Rust structs as CSV records. The fields of the struct are used to write a header row automatically. (Writing the header row automatically can be disabled by building the CSV writer with a WriterBuilder and calling the has_headers method.)

use std::error::Error;

use csv_stream::WriterBuilder;
use serde::Serialize;

#[derive(Serialize)]
struct Row<'a> {
    city: &'a str,
    country: &'a str,
    // Serde allows us to name our headers exactly,
    // even if they don't match our struct field names.
    #[serde(rename = "popcount")]
    population: u64,
}

fn example() -> Result<(), Box<dyn Error>> {
    let mut wtr = WriterBuilder::default().build();
    let mut buf = vec![];
    wtr.serialize(
        &mut buf,
        Row {
            city: "Boston",
            country: "United States",
            population: 4628910,
        },
    )?;
    wtr.serialize(
        &mut buf,
        Row {
            city: "Concord",
            country: "United States",
            population: 42695,
        },
    )?;

    let data = String::from_utf8(buf)?;
    assert_eq!(data, "\
city,country,popcount
Boston,United States,4628910
Concord,United States,42695
");
    Ok(())
}
§Rules

The behavior of serialize is fairly simple:

  1. Nested containers (tuples, Vecs, structs, etc.) are always flattened (depth-first order).

  2. If has_headers is true and the type contains field names, then a header row is automatically generated.

However, some container types cannot be serialized, and if has_headers is true, there are some additional restrictions on the types that can be serialized. See below for details.

For the purpose of this section, Rust types can be divided into three categories: scalars, non-struct containers, and structs.

§Scalars

Single values with no field names are written like the following. Note that some of the outputs may be quoted, according to the selected quoting style.

NameExample TypeExample ValueOutput
booleanbooltruetrue
integersi8, i16, i32, i64, i128, u8, u16, u32, u64, u12855
floatsf32, f643.143.14
characterchar'☃'
string&str"hi"hi
bytes&[u8]b"hi"[..]hi
optionOptionNoneempty
optionSome(5)5
unit()()empty
unit structstruct Foo;FooFoo
unit enum variantenum E { A, B }E::AA
newtype structstruct Foo(u8);Foo(5)5
newtype enum variantenum E { A(u8) }E::A(5)5

Note that this table includes simple structs and enums. For example, to serialize a field from either an integer or a float type, one can do this:

use std::error::Error;

use csv_stream::WriterBuilder;
use serde::Serialize;

#[derive(Serialize)]
struct Row {
    label: String,
    value: Value,
}

#[derive(Serialize)]
enum Value {
    Integer(i64),
    Float(f64),
}

fn example() -> Result<(), Box<dyn Error>> {
    let mut wtr = WriterBuilder::default().build();
    let mut buf = vec![];
    wtr.serialize(
        &mut buf,
        Row {
            label: "foo".to_string(),
            value: Value::Integer(3),
        },
    )?;
    wtr.serialize(
        &mut buf,
        Row {
            label: "bar".to_string(),
            value: Value::Float(3.14),
        },
    )?;

    let data = String::from_utf8(buf)?;
    assert_eq!(data, "\
label,value
foo,3
bar,3.14
");
    Ok(())
}
§Non-Struct Containers

Nested containers are flattened to their scalar components, with the exception of a few types that are not allowed:

NameExample TypeExample ValueOutput
sequenceVec<u8>vec![1, 2, 3]1,2,3
tuple(u8, bool)(5, true)5,true
tuple structFoo(u8, bool)Foo(5, true)5,true
tuple enum variantenum E { A(u8, bool) }E::A(5, true)error
struct enum variantenum E { V { a: u8, b: bool } }E::V { a: 5, b: true }error
mapBTreeMap<K, V>BTreeMap::new()error
§Structs

Like the other containers, structs are flattened to their scalar components:

NameExample TypeExample ValueOutput
structstruct Foo { a: u8, b: bool }Foo { a: 5, b: true }5,true

If has_headers is false, then there are no additional restrictions; types can be nested arbitrarily. For example:

use std::error::Error;

use csv_stream::WriterBuilder;
use serde::Serialize;

#[derive(Serialize)]
struct Row {
    label: String,
    values: Vec<f64>,
}

fn example() -> Result<(), Box<dyn Error>> {
    let mut wtr = WriterBuilder::default()
        .has_headers(false)
        .build();

    let mut buf = vec![];
    wtr.serialize(
        &mut buf,
        Row {
            label: "foo".to_string(),
            values: vec![1.1234, 2.5678, 3.14],
        },
    )?;

    let data = String::from_utf8(buf)?;
    assert_eq!(data, "\
foo,1.1234,2.5678,3.14
");
    Ok(())
}

However, if has_headers were enabled in the above example, then serialization would return an error. Specifically, when has_headers is true, there are two restrictions:

  1. Named field values in structs must be scalars.

  2. All scalars must be named field values in structs.

Other than these two restrictions, types can be nested arbitrarily. Here are a few examples:

ValueHeaderRecord
(Foo { x: 5, y: 6 }, Bar { z: true })x,y,z5,6,true
vec![Foo { x: 5, y: 6 }, Foo { x: 7, y: 8 }]x,y,x,y5,6,7,8
(Foo { x: 5, y: 6 }, vec![Bar { z: Baz(true) }])x,y,z5,6,true
Foo { x: 5, y: (6, 7) }error: restriction 15,6,7
(5, Foo { x: 6, y: 7 }error: restriction 25,6,7
(Foo { x: 5, y: 6 }, true)error: restriction 25,6,true
Source

pub fn write_record<I, T>(&mut self, buf: &mut Vec<u8>, record: I) -> Result<()>
where I: IntoIterator<Item = T>, T: AsRef<[u8]>,

Write a single record.

This method accepts something that can be turned into an iterator that yields elements that can be represented by a &[u8].

This may be called with an empty iterator, which will cause a record terminator to be written. If no fields had been written, then a single empty field is written before the terminator.

§Example
use std::error::Error;
use csv_stream::WriterBuilder;

fn example() -> Result<(), Box<dyn Error>> {
    let mut wtr = WriterBuilder::default().build();
    let mut buf = vec![];
    wtr.write_record(&mut buf, &["a", "b", "c"])?;
    wtr.write_record(&mut buf, &["x", "y", "z"])?;

    let data = String::from_utf8(buf)?;
    assert_eq!(data, "a,b,c\nx,y,z\n");
    Ok(())
}
Source

pub fn write_field<T: AsRef<[u8]>>( &mut self, buf: &mut Vec<u8>, field: T, ) -> Result<()>

Write a single field.

One should prefer using write_record over this method. It is provided for cases where writing a field at a time is more convenient than writing a record at a time.

Note that if this API is used, write_record should be called with an empty iterator to write a record terminator.

§Example
use std::error::Error;
use csv_stream::WriterBuilder;

fn example() -> Result<(), Box<dyn Error>> {
    let mut wtr = WriterBuilder::default().build();
    let mut buf = vec![];
    wtr.write_field(&mut buf, "a")?;
    wtr.write_field(&mut buf, "b")?;
    wtr.write_field(&mut buf, "c")?;
    wtr.write_record(&mut buf, None::<&[u8]>)?;
    wtr.write_field(&mut buf, "x")?;
    wtr.write_field(&mut buf, "y")?;
    wtr.write_field(&mut buf, "z")?;
    wtr.write_record(&mut buf, None::<&[u8]>)?;

    let data = String::from_utf8(buf)?;
    assert_eq!(data, "a,b,c\nx,y,z\n");
    Ok(())
}

Trait Implementations§

Source§

impl Debug for Writer

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Writer

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl Freeze for Writer

§

impl RefUnwindSafe for Writer

§

impl Send for Writer

§

impl Sync for Writer

§

impl Unpin for Writer

§

impl UnwindSafe for Writer

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.