CSV Streaming Reader Deserializer

Stream CSV files record by record with the csv crate, validating deserialization per row and quarantining malformed lines.

RustData Integrity

Code

snippet
use serde::Deserialize;
use std::io::Cursor;

#[derive(Debug, Deserialize)]
struct Subscriber {
    #[serde(rename = "Email")]
    email: String,
    #[serde(rename = "Plan")]
    plan: String,
    #[serde(rename = "Seats")]
    seats: u32,
}

fn stream_subscribers(
    raw: &str,
) -> Result<(Vec<Subscriber>, Vec<(usize, String)>), csv::Error> {
    let mut reader = csv::ReaderBuilder::new()
        .has_headers(true)
        .trim(csv::Trim::All)
        .flexible(false)
        .from_reader(Cursor::new(raw));

    let mut valid = Vec::new();
    let mut rejected: Vec<(usize, String)> = Vec::new();
    let mut line = 1;

    for outcome in reader.deserialize::<Subscriber>() {
        line += 1;
        match outcome {
            Ok(record) => valid.push(record),
            Err(err) => {
                // 单行结构或类型错误:隔离并保留行号
                rejected.push((line, err.to_string()));
            }
        }
    }
    Ok((valid, rejected))
}

fn main() {
    let payload = "Email,Plan,Seats\n\
[email protected],pro,10\n\
[email protected],basic,not-a-number\n\
[email protected],enterprise,250\n\
broken-line-only-one-field\n";

    match stream_subscribers(payload) {
        Ok((valid, rejected)) => {
            for record in &valid {
                println!("{} on {} x{}", record.email, record.plan, record.seats);
            }
            for (line, reason) in &rejected {
                println!("line {line} rejected: {reason}");
            }
            println!("valid: {}, rejected: {}", valid.len(), rejected.len());
        }
        Err(err) => println!("fatal csv error: {err}"),
    }
}