From b15775e8ff3cdfb1aaaefc86ac9512a68553bb4d Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 29 Oct 2025 12:30:31 +0100 Subject: [PATCH 1/5] Added serde as optional feature --- Cargo.toml | 164 ++++++++++++++++++++- ci/script.sh | 1 + examples/cookbook-read-serde.rs | 1 + examples/cookbook-write-serde.rs | 1 + examples/tutorial-perf-serde-01.rs | 1 + examples/tutorial-perf-serde-02.rs | 1 + examples/tutorial-perf-serde-03.rs | 1 + examples/tutorial-pipeline-pop-01.rs | 1 + examples/tutorial-read-serde-02.rs | 1 + examples/tutorial-read-serde-03.rs | 1 + examples/tutorial-read-serde-04.rs | 1 + examples/tutorial-read-serde-invalid-01.rs | 1 + examples/tutorial-read-serde-invalid-02.rs | 1 + examples/tutorial-write-serde-01.rs | 1 + examples/tutorial-write-serde-02.rs | 1 + src/byte_record.rs | 12 +- src/cookbook.rs | 14 ++ src/deserializer.rs | 1 + src/error.rs | 13 +- src/lib.rs | 30 ++-- src/reader.rs | 34 +++-- src/serializer.rs | 1 + src/string_record.rs | 8 +- src/tutorial.rs | 67 +++++++++ src/writer.rs | 48 +++--- 25 files changed, 345 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7101062c..e3b3e3b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,14 +24,176 @@ bench = false csv-core = { path = "csv-core", version = "0.1.11" } itoa = "1" ryu = "1" -serde_core = "1.0.221" +serde_core = {version = "1.0.221", optional = true } [dev-dependencies] bstr = { version = "1.7.0", default-features = false, features = ["alloc", "serde"] } serde = { version = "1.0.221", features = ["derive"] } +[features] +default = ["serde"] +serde = ["dep:serde_core"] + [profile.release] debug = true [profile.bench] debug = true + +[[example]] +name = "cookbook-read-basic" +path = "examples/cookbook-read-basic.rs" + +[[example]] +name = "cookbook-read-colon" +path = "examples/cookbook-read-colon.rs" + +[[example]] +name = "cookbook-read-no-headers" +path = "examples/cookbook-read-no-headers.rs" + +[[example]] +name = "cookbook-read-serde" +path = "examples/cookbook-read-serde.rs" +required-features = ["serde"] + +[[example]] +name = "cookbook-write-basic" +path = "examples/cookbook-write-basic.rs" + +[[example]] +name = "cookbook-write-serde" +path = "examples/cookbook-write-serde.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-error-01" +path = "examples/tutorial-error-01.rs" + +[[example]] +name = "tutorial-error-02" +path = "examples/tutorial-error-02.rs" + +[[example]] +name = "tutorial-error-03" +path = "examples/tutorial-error-03.rs" + +[[example]] +name = "tutorial-error-04" +path = "examples/tutorial-error-04.rs" + +[[example]] +name = "tutorial-perf-alloc-01" +path = "examples/tutorial-perf-alloc-01.rs" + +[[example]] +name = "tutorial-perf-alloc-02" +path = "examples/tutorial-perf-alloc-02.rs" + +[[example]] +name = "tutorial-perf-alloc-03" +path = "examples/tutorial-perf-alloc-03.rs" + +[[example]] +name = "tutorial-perf-core-01" +path = "examples/tutorial-perf-core-01.rs" + +[[example]] +name = "tutorial-perf-serde-01" +path = "examples/tutorial-perf-serde-01.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-perf-serde-02" +path = "examples/tutorial-perf-serde-02.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-perf-serde-03" +path = "examples/tutorial-perf-serde-03.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-pipeline-pop-01" +path = "examples/tutorial-pipeline-pop-01.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-pipeline-search-01" +path = "examples/tutorial-pipeline-search-01.rs" + +[[example]] +name = "tutorial-pipeline-search-02" +path = "examples/tutorial-pipeline-search-02.rs" + +[[example]] +name = "tutorial-read-01" +path = "examples/tutorial-read-01.rs" + +[[example]] +name = "tutorial-read-delimiter-01" +path = "examples/tutorial-read-delimiter-01.rs" + +[[example]] +name = "tutorial-read-headers-01" +path = "examples/tutorial-read-headers-01.rs" + +[[example]] +name = "tutorial-read-headers-02" +path = "examples/tutorial-read-headers-02.rs" + +[[example]] +name = "tutorial-read-serde-01" +path = "examples/tutorial-read-serde-01.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-read-serde-02" +path = "examples/tutorial-read-serde-02.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-read-serde-03" +path = "examples/tutorial-read-serde-03.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-read-serde-04" +path = "examples/tutorial-read-serde-04.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-read-serde-invalid-01" +path = "examples/tutorial-read-serde-invalid-01.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-read-serde-invalid-02" +path = "examples/tutorial-read-serde-invalid-02.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-setup-01" +path = "examples/tutorial-setup-01.rs" + +[[example]] +name = "tutorial-write-01" +path = "examples/tutorial-write-01.rs" + +[[example]] +name = "tutorial-write-02" +path = "examples/tutorial-write-02.rs" + +[[example]] +name = "tutorial-write-delimiter-01" +path = "examples/tutorial-write-delimiter-01.rs" + +[[example]] +name = "tutorial-write-serde-01" +path = "examples/tutorial-write-serde-01.rs" +required-features = ["serde"] + +[[example]] +name = "tutorial-write-serde-02" +path = "examples/tutorial-write-serde-02.rs" +required-features = ["serde"] \ No newline at end of file diff --git a/ci/script.sh b/ci/script.sh index 0611b776..37d0c578 100755 --- a/ci/script.sh +++ b/ci/script.sh @@ -12,6 +12,7 @@ if [ "$TRAVIS_RUST_VERSION" = "1.33.0" ]; then fi cargo test --verbose +cargo test --verbose --no-default-features cargo test --verbose --manifest-path csv-core/Cargo.toml cargo test --verbose --manifest-path csv-index/Cargo.toml if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then diff --git a/examples/cookbook-read-serde.rs b/examples/cookbook-read-serde.rs index 20a8399c..83c4bfb9 100644 --- a/examples/cookbook-read-serde.rs +++ b/examples/cookbook-read-serde.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; diff --git a/examples/cookbook-write-serde.rs b/examples/cookbook-write-serde.rs index b6c3cbbe..8d1cf212 100644 --- a/examples/cookbook-write-serde.rs +++ b/examples/cookbook-write-serde.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::{error::Error, io, process}; use serde::Serialize; diff --git a/examples/tutorial-perf-serde-01.rs b/examples/tutorial-perf-serde-01.rs index 65781529..83aaeb70 100644 --- a/examples/tutorial-perf-serde-01.rs +++ b/examples/tutorial-perf-serde-01.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; diff --git a/examples/tutorial-perf-serde-02.rs b/examples/tutorial-perf-serde-02.rs index b510ee95..31ad92e5 100644 --- a/examples/tutorial-perf-serde-02.rs +++ b/examples/tutorial-perf-serde-02.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] #![allow(dead_code)] use serde::Deserialize; use std::{error::Error, io, process}; diff --git a/examples/tutorial-perf-serde-03.rs b/examples/tutorial-perf-serde-03.rs index 1b9b5fa7..d871af02 100644 --- a/examples/tutorial-perf-serde-03.rs +++ b/examples/tutorial-perf-serde-03.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; diff --git a/examples/tutorial-pipeline-pop-01.rs b/examples/tutorial-pipeline-pop-01.rs index 3f7d400c..bf6ac309 100644 --- a/examples/tutorial-pipeline-pop-01.rs +++ b/examples/tutorial-pipeline-pop-01.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::{env, error::Error, io, process}; use serde::{Deserialize, Serialize}; diff --git a/examples/tutorial-read-serde-02.rs b/examples/tutorial-read-serde-02.rs index 2cdcd91c..d098e1e0 100644 --- a/examples/tutorial-read-serde-02.rs +++ b/examples/tutorial-read-serde-02.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::{error::Error, io, process}; // This introduces a type alias so that we can conveniently reference our diff --git a/examples/tutorial-read-serde-03.rs b/examples/tutorial-read-serde-03.rs index 022e2460..ed352f45 100644 --- a/examples/tutorial-read-serde-03.rs +++ b/examples/tutorial-read-serde-03.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::collections::HashMap; use std::{error::Error, io, process}; diff --git a/examples/tutorial-read-serde-04.rs b/examples/tutorial-read-serde-04.rs index 796040cb..deb6e957 100644 --- a/examples/tutorial-read-serde-04.rs +++ b/examples/tutorial-read-serde-04.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; diff --git a/examples/tutorial-read-serde-invalid-01.rs b/examples/tutorial-read-serde-invalid-01.rs index 3ea836d9..24bf7a33 100644 --- a/examples/tutorial-read-serde-invalid-01.rs +++ b/examples/tutorial-read-serde-invalid-01.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; diff --git a/examples/tutorial-read-serde-invalid-02.rs b/examples/tutorial-read-serde-invalid-02.rs index b4426cf5..01114662 100644 --- a/examples/tutorial-read-serde-invalid-02.rs +++ b/examples/tutorial-read-serde-invalid-02.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; diff --git a/examples/tutorial-write-serde-01.rs b/examples/tutorial-write-serde-01.rs index 0c7146db..bdac93aa 100644 --- a/examples/tutorial-write-serde-01.rs +++ b/examples/tutorial-write-serde-01.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::{error::Error, io, process}; fn run() -> Result<(), Box> { diff --git a/examples/tutorial-write-serde-02.rs b/examples/tutorial-write-serde-02.rs index 8d298fd7..65735540 100644 --- a/examples/tutorial-write-serde-02.rs +++ b/examples/tutorial-write-serde-02.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::{error::Error, io, process}; use serde::Serialize; diff --git a/src/byte_record.rs b/src/byte_record.rs index c6c196e4..63585f04 100644 --- a/src/byte_record.rs +++ b/src/byte_record.rs @@ -5,11 +5,8 @@ use std::{ result, }; -use serde_core::de::Deserialize; - use crate::{ - deserializer::deserialize_byte_record, - error::{new_utf8_error, Result, Utf8Error}, + error::{new_utf8_error, Utf8Error}, string_record::StringRecord, }; @@ -144,6 +141,7 @@ impl ByteRecord { })) } + #[cfg(feature = "serde")] /// Deserialize this record. /// /// The `D` type parameter refers to the type that this record should be @@ -229,11 +227,11 @@ impl ByteRecord { /// Ok(()) /// } /// ``` - pub fn deserialize<'de, D: Deserialize<'de>>( + pub fn deserialize<'de, D: serde_core::de::Deserialize<'de>>( &'de self, headers: Option<&'de ByteRecord>, - ) -> Result { - deserialize_byte_record(self, headers) + ) -> crate::error::Result { + crate::deserializer::deserialize_byte_record(self, headers) } /// Returns an iterator over all fields in this record. diff --git a/src/cookbook.rs b/src/cookbook.rs index 4d3a5775..0bdddc7b 100644 --- a/src/cookbook.rs +++ b/src/cookbook.rs @@ -73,6 +73,7 @@ method. ```no_run # //cookbook-read-serde.rs # #![allow(dead_code)] +# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; use serde::Deserialize; @@ -104,6 +105,12 @@ fn main() { process::exit(1); } } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` The above example can be run like so: @@ -232,6 +239,7 @@ headers are written automatically. ```no_run # //cookbook-write-serde.rs +# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; use serde::Serialize; @@ -271,6 +279,12 @@ fn main() { process::exit(1); } } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` The above example can be run like so: diff --git a/src/deserializer.rs b/src/deserializer.rs index c7c32178..5e7db718 100644 --- a/src/deserializer.rs +++ b/src/deserializer.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::{error::Error as StdError, fmt, iter, num, str}; use serde_core::{ diff --git a/src/error.rs b/src/error.rs index 8ffc181b..bb1317e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,6 @@ use std::{error::Error as StdError, fmt, io, result}; -use crate::{ - byte_record::{ByteRecord, Position}, - deserializer::DeserializeError, -}; +use crate::byte_record::{ByteRecord, Position}; /// A type alias for `Result`. pub type Result = result::Result; @@ -85,15 +82,17 @@ pub enum ErrorKind { /// are called on a CSV reader that was asked to `seek` before it parsed /// the first record. Seek, + #[cfg(feature = "serde")] /// An error of this kind occurs only when using the Serde serializer. Serialize(String), + #[cfg(feature = "serde")] /// An error of this kind occurs only when performing automatic /// deserialization with serde. Deserialize { /// The position of this error, if available. pos: Option, /// The deserialization error. - err: DeserializeError, + err: crate::deserializer::DeserializeError, }, } @@ -106,6 +105,7 @@ impl ErrorKind { match *self { ErrorKind::Utf8 { ref pos, .. } => pos.as_ref(), ErrorKind::UnequalLengths { ref pos, .. } => pos.as_ref(), + #[cfg(feature = "serde")] ErrorKind::Deserialize { ref pos, .. } => pos.as_ref(), _ => None, } @@ -173,12 +173,15 @@ impl fmt::Display for Error { when the parser was seeked before the first record \ could be read" ), + #[cfg(feature = "serde")] ErrorKind::Serialize(ref err) => { write!(f, "CSV write error: {}", err) } + #[cfg(feature = "serde")] ErrorKind::Deserialize { pos: None, ref err } => { write!(f, "CSV deserialize error: {}", err) } + #[cfg(feature = "serde")] ErrorKind::Deserialize { pos: Some(ref pos), ref err } => write!( f, "CSV deserialize error: record {} \ diff --git a/src/lib.rs b/src/lib.rs index 28ff12ad..5b99c0cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ By default, the member names of the struct are matched with the values in the header record of your CSV data. ```no_run +# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; #[derive(Debug, serde::Deserialize)] @@ -121,6 +122,12 @@ fn main() { process::exit(1); } } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` The above example can be run like so: @@ -135,20 +142,19 @@ $ cargo run --example cookbook-read-serde < examples/data/smallpop.csv #![deny(missing_docs)] -use std::result; - -use serde_core::{Deserialize, Deserializer}; +#[cfg(feature = "serde")] +pub use crate::deserializer::{DeserializeError, DeserializeErrorKind}; +#[cfg(feature = "serde")] +pub use crate::reader::{DeserializeRecordsIntoIter, DeserializeRecordsIter}; pub use crate::{ byte_record::{ByteRecord, ByteRecordIter, Position}, - deserializer::{DeserializeError, DeserializeErrorKind}, error::{ Error, ErrorKind, FromUtf8Error, IntoInnerError, Result, Utf8Error, }, reader::{ - ByteRecordsIntoIter, ByteRecordsIter, DeserializeRecordsIntoIter, - DeserializeRecordsIter, Reader, ReaderBuilder, StringRecordsIntoIter, - StringRecordsIter, + ByteRecordsIntoIter, ByteRecordsIter, Reader, ReaderBuilder, + StringRecordsIntoIter, StringRecordsIter, }, string_record::{StringRecord, StringRecordIter}, writer::{Writer, WriterBuilder}, @@ -248,6 +254,7 @@ impl Trim { } } +#[cfg(feature = "serde")] /// A custom Serde deserializer for possibly invalid `Option` fields. /// /// When deserializing CSV data, it is sometimes desirable to simply ignore @@ -301,10 +308,13 @@ impl Trim { /// } /// } /// ``` -pub fn invalid_option<'de, D, T>(de: D) -> result::Result, D::Error> +pub fn invalid_option<'de, D, T>( + de: D, +) -> std::result::Result, D::Error> where - D: Deserializer<'de>, - Option: Deserialize<'de>, + D: serde_core::de::Deserializer<'de>, + Option: serde_core::de::Deserialize<'de>, { + use serde_core::Deserialize; Option::::deserialize(de).or_else(|_| Ok(None)) } diff --git a/src/reader.rs b/src/reader.rs index a34c9d5a..85adf9b7 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,15 +1,11 @@ use std::{ fs::File, io::{self, BufRead, Seek}, - marker::PhantomData, path::Path, result, }; -use { - csv_core::{Reader as CoreReader, ReaderBuilder as CoreReaderBuilder}, - serde_core::de::DeserializeOwned, -}; +use csv_core::{Reader as CoreReader, ReaderBuilder as CoreReaderBuilder}; use crate::{ byte_record::{ByteRecord, Position}, @@ -870,6 +866,7 @@ impl Reader { ReaderBuilder::new().from_reader(rdr) } + #[cfg(feature = "serde")] /// Returns a borrowed iterator over deserialized records. /// /// Each item yielded by this iterator is a `Result`. @@ -1047,11 +1044,12 @@ impl Reader { /// ``` pub fn deserialize(&mut self) -> DeserializeRecordsIter<'_, R, D> where - D: DeserializeOwned, + D: serde_core::de::DeserializeOwned, { DeserializeRecordsIter::new(self) } + #[cfg(feature = "serde")] /// Returns an owned iterator over deserialized records. /// /// Each item yielded by this iterator is a `Result`. @@ -1107,7 +1105,7 @@ impl Reader { /// ``` pub fn into_deserialize(self) -> DeserializeRecordsIntoIter where - D: DeserializeOwned, + D: serde_core::de::DeserializeOwned, { DeserializeRecordsIntoIter::new(self) } @@ -1900,6 +1898,7 @@ impl ReaderState { } } +#[cfg(feature = "serde")] /// An owned iterator over deserialized records. /// /// The type parameter `R` refers to the underlying `io::Read` type, and `D` @@ -1908,10 +1907,11 @@ pub struct DeserializeRecordsIntoIter { rdr: Reader, rec: StringRecord, headers: Option, - _priv: PhantomData, + _priv: std::marker::PhantomData, } -impl DeserializeRecordsIntoIter { +#[cfg(feature = "serde")] +impl DeserializeRecordsIntoIter { fn new(mut rdr: Reader) -> DeserializeRecordsIntoIter { let headers = if !rdr.state.has_headers { None @@ -1922,7 +1922,7 @@ impl DeserializeRecordsIntoIter { rdr, rec: StringRecord::new(), headers, - _priv: PhantomData, + _priv: std::marker::PhantomData, } } @@ -1942,7 +1942,8 @@ impl DeserializeRecordsIntoIter { } } -impl Iterator +#[cfg(feature = "serde")] +impl Iterator for DeserializeRecordsIntoIter { type Item = Result; @@ -1956,6 +1957,7 @@ impl Iterator } } +#[cfg(feature = "serde")] /// A borrowed iterator over deserialized records. /// /// The lifetime parameter `'r` refers to the lifetime of the underlying @@ -1966,10 +1968,11 @@ pub struct DeserializeRecordsIter<'r, R: 'r, D> { rdr: &'r mut Reader, rec: StringRecord, headers: Option, - _priv: PhantomData, + _priv: std::marker::PhantomData, } -impl<'r, R: io::Read, D: DeserializeOwned> DeserializeRecordsIter<'r, R, D> { +#[cfg(feature = "serde")] +impl<'r, R: io::Read, D> DeserializeRecordsIter<'r, R, D> { fn new(rdr: &'r mut Reader) -> DeserializeRecordsIter<'r, R, D> { let headers = if !rdr.state.has_headers { None @@ -1980,7 +1983,7 @@ impl<'r, R: io::Read, D: DeserializeOwned> DeserializeRecordsIter<'r, R, D> { rdr, rec: StringRecord::new(), headers, - _priv: PhantomData, + _priv: std::marker::PhantomData, } } @@ -1995,7 +1998,8 @@ impl<'r, R: io::Read, D: DeserializeOwned> DeserializeRecordsIter<'r, R, D> { } } -impl<'r, R: io::Read, D: DeserializeOwned> Iterator +#[cfg(feature = "serde")] +impl<'r, R: io::Read, D: serde_core::de::DeserializeOwned> Iterator for DeserializeRecordsIter<'r, R, D> { type Item = Result; diff --git a/src/serializer.rs b/src/serializer.rs index a3f9ff82..56189c26 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "serde")] use std::{fmt, io, mem}; use serde_core::ser::{ diff --git a/src/string_record.rs b/src/string_record.rs index d9001e24..5bd4e532 100644 --- a/src/string_record.rs +++ b/src/string_record.rs @@ -5,11 +5,8 @@ use std::{ result, str, }; -use serde_core::de::Deserialize; - use crate::{ byte_record::{ByteRecord, ByteRecordIter, Position}, - deserializer::deserialize_string_record, error::{Error, ErrorKind, FromUtf8Error, Result}, reader::Reader, }; @@ -206,6 +203,7 @@ impl StringRecord { str_record } + #[cfg(feature = "serde")] /// Deserialize this record. /// /// The `D` type parameter refers to the type that this record should be @@ -289,11 +287,11 @@ impl StringRecord { /// Ok(()) /// } /// ``` - pub fn deserialize<'de, D: Deserialize<'de>>( + pub fn deserialize<'de, D: serde_core::de::Deserialize<'de>>( &'de self, headers: Option<&'de StringRecord>, ) -> Result { - deserialize_string_record(self, headers) + crate::deserializer::deserialize_string_record(self, headers) } /// Returns an iterator over all fields in this record. diff --git a/src/tutorial.rs b/src/tutorial.rs index 46820534..e8a00c90 100644 --- a/src/tutorial.rs +++ b/src/tutorial.rs @@ -744,6 +744,7 @@ type: `(String, String, Option, f64, f64)`. ```no_run //tutorial-read-serde-02.rs +# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; # // This introduces a type alias so that we can conveniently reference our @@ -768,6 +769,12 @@ fn run() -> Result<(), Box> { # process::exit(1); # } # } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` Running this code should show similar output as previous examples: @@ -795,6 +802,7 @@ a new `use` statement that imports `HashMap` from the standard library: ```no_run //tutorial-read-serde-03.rs +# #[cfg(feature = "serde")] { use std::collections::HashMap; # use std::{error::Error, io, process}; @@ -817,6 +825,12 @@ fn run() -> Result<(), Box> { # process::exit(1); # } # } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` Running this program shows similar results as before, but each record is @@ -853,6 +867,7 @@ how. Don't miss the new Serde imports! ```no_run //tutorial-read-serde-04.rs # #![allow(dead_code)] +# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; // This lets us write `#[derive(Deserialize)]`. @@ -890,6 +905,7 @@ fn main() { process::exit(1); } } +# } ``` Compile and run this program to see similar output as before: @@ -990,6 +1006,7 @@ Let's start by running our program from the previous section: ```no_run //tutorial-read-serde-invalid-01.rs # #![allow(dead_code)] +# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; # # use serde::Deserialize; @@ -1019,6 +1036,7 @@ fn run() -> Result<(), Box> { # process::exit(1); # } # } +# } ``` Compile and run it on our messier data: @@ -1058,6 +1076,7 @@ to a `None` value, as shown in this next example: ```no_run //tutorial-read-serde-invalid-02.rs # #![allow(dead_code)] +# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; # # use serde::Deserialize; @@ -1087,6 +1106,12 @@ fn run() -> Result<(), Box> { # process::exit(1); # } # } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` If you compile and run this example, then it should run to completion just @@ -1350,6 +1375,7 @@ As with reading, let's start by seeing how we can serialize a Rust tuple. ```no_run //tutorial-write-serde-01.rs +# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; # fn run() -> Result<(), Box> { @@ -1378,6 +1404,12 @@ fn run() -> Result<(), Box> { # process::exit(1); # } # } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` Compiling and running this program gives the expected output: @@ -1418,6 +1450,7 @@ shown in the example: ```no_run //tutorial-write-serde-02.rs +# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; use serde::Serialize; @@ -1468,6 +1501,12 @@ fn main() { process::exit(1); } } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` Compiling and running this example has the same output as last time, even @@ -1731,6 +1770,7 @@ Now here's the code: ```no_run //tutorial-pipeline-pop-01.rs +# #[cfg(feature = "serde")] { # use std::{env, error::Error, io, process}; use serde::{Deserialize, Serialize}; @@ -1788,6 +1828,12 @@ fn main() { process::exit(1); } } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` If we compile and run our program with a minimum threshold of `100000`, we @@ -2076,6 +2122,7 @@ example using Serde in a previous section: ```no_run //tutorial-perf-serde-01.rs # #![allow(dead_code)] +# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; use serde::Deserialize; @@ -2116,6 +2163,12 @@ fn main() { } } } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` Now compile and run this program: @@ -2144,6 +2197,7 @@ like: ```no_run //tutorial-perf-serde-02.rs # #![allow(dead_code)] +# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; # use serde::Deserialize; # @@ -2185,6 +2239,12 @@ fn run() -> Result> { # } # } # } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` Compile and run: @@ -2229,6 +2289,7 @@ of `StringRecord`: ```no_run //tutorial-perf-serde-03.rs # #![allow(dead_code)] +# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; # # use serde::Deserialize; @@ -2271,6 +2332,12 @@ fn run() -> Result> { # } # } # } +# } +# #[cfg(not(feature = "serde"))] { +# fn main() { +# println!("this example requires the 'serde' feature"); +# } +# } ``` Compile and run: diff --git a/src/writer.rs b/src/writer.rs index 195e663e..bfe45782 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -1,17 +1,13 @@ use std::{fs::File, io, path::Path, result}; -use { - csv_core::{ - self, WriteResult, Writer as CoreWriter, - WriterBuilder as CoreWriterBuilder, - }, - serde_core::Serialize, +use csv_core::{ + self, WriteResult, Writer as CoreWriter, + WriterBuilder as CoreWriterBuilder, }; use crate::{ byte_record::ByteRecord, error::{Error, ErrorKind, IntoInnerError, Result}, - serializer::{serialize, serialize_header}, {QuoteStyle, Terminator}, }; @@ -164,6 +160,7 @@ impl WriterBuilder { /// names of a struct. /// /// ``` + /// # #[cfg(feature = "serde")] { /// use std::error::Error; /// /// use csv::WriterBuilder; @@ -200,6 +197,7 @@ impl WriterBuilder { /// "); /// Ok(()) /// } + /// # } /// ``` /// /// # Example: without headers @@ -210,6 +208,7 @@ impl WriterBuilder { /// explicitly want to both write custom headers and serialize structs. /// /// ``` + /// # #[cfg(feature = "serde")] { /// use std::error::Error; /// use csv::WriterBuilder; /// @@ -226,6 +225,7 @@ impl WriterBuilder { /// "); /// Ok(()) /// } + /// # } /// ``` pub fn has_headers(&mut self, yes: bool) -> &mut WriterBuilder { self.has_headers = yes; @@ -542,6 +542,7 @@ pub struct Writer { #[derive(Debug)] struct WriterState { + #[cfg(feature = "serde")] /// Whether the Serde serializer should attempt to write a header row. header: HeaderState, /// Whether inconsistent record lengths are allowed. @@ -561,6 +562,7 @@ struct WriterState { /// HeaderState encodes a small state machine for handling header writes. #[derive(Debug)] +#[cfg(feature = "serde")] enum HeaderState { /// Indicates that we should attempt to write a header. Write, @@ -623,17 +625,17 @@ impl Writer { impl Writer { fn new(builder: &WriterBuilder, wtr: W) -> Writer { - let header_state = if builder.has_headers { - HeaderState::Write - } else { - HeaderState::None - }; Writer { core: builder.builder.build(), wtr: Some(wtr), buf: Buffer { buf: vec![0; builder.capacity], len: 0 }, state: WriterState { - header: header_state, + #[cfg(feature = "serde")] + header: if builder.has_headers { + HeaderState::Write + } else { + HeaderState::None + }, flexible: builder.flexible, first_field_count: None, fields_written: 0, @@ -669,6 +671,7 @@ impl Writer { WriterBuilder::new().from_writer(wtr) } + #[cfg(feature = "serde")] /// Serialize a single record using Serde. /// /// # Example @@ -874,9 +877,13 @@ impl Writer { /// | `Foo { x: 5, y: (6, 7) }` | *error: restriction 1* | `5,6,7` | /// | `(5, Foo { x: 6, y: 7 }` | *error: restriction 2* | `5,6,7` | /// | `(Foo { x: 5, y: 6 }, true)` | *error: restriction 2* | `5,6,true` | - pub fn serialize(&mut self, record: S) -> Result<()> { + pub fn serialize( + &mut self, + record: S, + ) -> Result<()> { if let HeaderState::Write = self.state.header { - let wrote_header = serialize_header(self, &record)?; + let wrote_header = + crate::serializer::serialize_header(self, &record)?; if wrote_header { self.write_terminator()?; self.state.header = HeaderState::DidWrite; @@ -884,7 +891,7 @@ impl Writer { self.state.header = HeaderState::DidNotWrite; }; } - serialize(self, &record)?; + crate::serializer::serialize(self, &record)?; self.write_terminator()?; Ok(()) } @@ -1216,8 +1223,6 @@ impl Buffer { mod tests { use std::io::{self, Write}; - use serde::Serialize; - use crate::{ byte_record::ByteRecord, error::ErrorKind, string_record::StringRecord, }; @@ -1387,7 +1392,9 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serialize_with_headers() { + use serde::Serialize; #[derive(Serialize)] struct Row { foo: i32, @@ -1401,7 +1408,9 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serialize_no_headers() { + use serde::Serialize; #[derive(Serialize)] struct Row { foo: i32, @@ -1416,7 +1425,9 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serialize_no_headers_128() { + use serde::Serialize; #[derive(Serialize)] struct Row { foo: i128, @@ -1436,6 +1447,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serialize_tuple() { let mut wtr = WriterBuilder::new().from_writer(vec![]); wtr.serialize((true, 1.3, "hi")).unwrap(); From 412d73318ac403e9ea8870195a695ea09c6f6529 Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 29 Oct 2025 13:15:37 +0100 Subject: [PATCH 2/5] Renamed python file --- ci/check-copy | 4 ++-- scripts/{copy-examples => copy-examples.py} | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename scripts/{copy-examples => copy-examples.py} (78%) diff --git a/ci/check-copy b/ci/check-copy index be6263ea..5f93103c 100755 --- a/ci/check-copy +++ b/ci/check-copy @@ -15,13 +15,13 @@ SOURCE="$1" errored() { rm -rf "$TMPDIR" - echo "HINT: please run scripts/copy-examples" >&2 + echo "HINT: please run scripts/copy-examples.py" >&2 exit 1 } # Make sure the right rustfmt config is available. cp "$REPO/rustfmt.toml" "$TMPDIR/" -"$SCRIPTS/copy-examples" \ +"$SCRIPTS/copy-examples.py" \ --rust-file "$REPO/src/$SOURCE.rs" \ --example-dir "$TMPDIR" for new in "$TMPDIR"/*.rs; do diff --git a/scripts/copy-examples b/scripts/copy-examples.py similarity index 78% rename from scripts/copy-examples rename to scripts/copy-examples.py index 6fb91ff3..00fd6e44 100755 --- a/scripts/copy-examples +++ b/scripts/copy-examples.py @@ -19,15 +19,15 @@ with codecs.open(args.rust_file, encoding='utf-8') as f: rustcode = f.read() - for m in RE_EACH_CODE_BLOCK.finditer(rustcode): - lines = m.group(1).splitlines() + for match in RE_EACH_CODE_BLOCK.finditer(rustcode): + lines = match.group(1).splitlines() marker, codelines = lines[0], lines[1:] - m = RE_MARKER.search(marker) - if m is None: + maybe_match = RE_MARKER.search(marker) + if maybe_match is None: continue code = '\n'.join(RE_STRIP_COMMENT.sub('', line) for line in codelines) - fpath = os.path.join(args.example_dir, m.group(1)) + fpath = os.path.join(args.example_dir, maybe_match.group(1)) with codecs.open(fpath, mode='w+', encoding='utf-8') as f: print(code, file=f) subprocess.check_output(['rustfmt', fpath]) From d4fbc9102ec779669a7dc1d981c07da56f3aedeb Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 29 Oct 2025 13:45:32 +0100 Subject: [PATCH 3/5] Tentatively fixed CI error regarding gated serde --- examples/cookbook-read-serde.rs | 12 ++- examples/cookbook-write-serde.rs | 12 ++- src/cookbook.rs | 24 +++--- src/lib.rs | 8 +- src/tutorial.rs | 129 ++++++++++++++----------------- src/writer.rs | 14 +++- 6 files changed, 100 insertions(+), 99 deletions(-) diff --git a/examples/cookbook-read-serde.rs b/examples/cookbook-read-serde.rs index 83c4bfb9..5b80989b 100644 --- a/examples/cookbook-read-serde.rs +++ b/examples/cookbook-read-serde.rs @@ -1,12 +1,10 @@ -#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; -use serde::Deserialize; - +#[cfg(feature = "serde")] // By default, struct field names are deserialized based on the position of // a corresponding field in the CSV data's header record. -#[derive(Debug, Deserialize)] +#[derive(Debug, serde::Deserialize)] struct Record { city: String, region: String, @@ -14,6 +12,7 @@ struct Record { population: Option, } +#[cfg(feature = "serde")] fn example() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -25,9 +24,14 @@ fn example() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "serde")] fn main() { if let Err(err) = example() { println!("error running example: {}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/cookbook-write-serde.rs b/examples/cookbook-write-serde.rs index 8d1cf212..702e9209 100644 --- a/examples/cookbook-write-serde.rs +++ b/examples/cookbook-write-serde.rs @@ -1,9 +1,7 @@ -#![cfg(feature = "serde")] use std::{error::Error, io, process}; -use serde::Serialize; - -#[derive(Debug, Serialize)] +#[cfg(feature = "serde")] +#[derive(Debug, serde::Serialize)] struct Record { city: String, region: String, @@ -11,6 +9,7 @@ struct Record { population: Option, } +#[cfg(feature = "serde")] fn example() -> Result<(), Box> { let mut wtr = csv::Writer::from_writer(io::stdout()); @@ -32,9 +31,14 @@ fn example() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "serde")] fn main() { if let Err(err) = example() { println!("error running example: {}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/src/cookbook.rs b/src/cookbook.rs index 0bdddc7b..30a54982 100644 --- a/src/cookbook.rs +++ b/src/cookbook.rs @@ -73,14 +73,12 @@ method. ```no_run # //cookbook-read-serde.rs # #![allow(dead_code)] -# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; -use serde::Deserialize; - +# #[cfg(feature = "serde")] // By default, struct field names are deserialized based on the position of // a corresponding field in the CSV data's header record. -#[derive(Debug, Deserialize)] +#[derive(Debug, serde::Deserialize)] struct Record { city: String, region: String, @@ -88,6 +86,7 @@ struct Record { population: Option, } +# #[cfg(feature = "serde")] fn example() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -99,18 +98,17 @@ fn example() -> Result<(), Box> { Ok(()) } +# #[cfg(feature = "serde")] fn main() { if let Err(err) = example() { println!("error running example: {}", err); process::exit(1); } } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` The above example can be run like so: @@ -239,12 +237,10 @@ headers are written automatically. ```no_run # //cookbook-write-serde.rs -# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; -use serde::Serialize; - -#[derive(Debug, Serialize)] +# #[cfg(feature = "serde")] +#[derive(Debug, serde::Serialize)] struct Record { city: String, region: String, @@ -252,6 +248,7 @@ struct Record { population: Option, } +# #[cfg(feature = "serde")] fn example() -> Result<(), Box> { let mut wtr = csv::Writer::from_writer(io::stdout()); @@ -273,18 +270,17 @@ fn example() -> Result<(), Box> { Ok(()) } +# #[cfg(feature = "serde")] fn main() { if let Err(err) = example() { println!("error running example: {}", err); process::exit(1); } } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` The above example can be run like so: diff --git a/src/lib.rs b/src/lib.rs index 5b99c0cc..f2d18ddd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,9 +94,9 @@ By default, the member names of the struct are matched with the values in the header record of your CSV data. ```no_run -# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; +# #[cfg(feature = "serde")] #[derive(Debug, serde::Deserialize)] struct Record { city: String, @@ -105,6 +105,7 @@ struct Record { population: Option, } +# #[cfg(feature = "serde")] fn example() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -116,18 +117,17 @@ fn example() -> Result<(), Box> { Ok(()) } +# #[cfg(feature = "serde")] fn main() { if let Err(err) = example() { println!("error running example: {}", err); process::exit(1); } } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` The above example can be run like so: diff --git a/src/tutorial.rs b/src/tutorial.rs index e8a00c90..3246a14d 100644 --- a/src/tutorial.rs +++ b/src/tutorial.rs @@ -744,13 +744,14 @@ type: `(String, String, Option, f64, f64)`. ```no_run //tutorial-read-serde-02.rs -# #[cfg(feature = "serde")] { +# #[allow(dead_code)] # use std::{error::Error, io, process}; # // This introduces a type alias so that we can conveniently reference our // record type. type Record = (String, String, Option, f64, f64); +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); // Instead of creating an iterator with the `records` method, we create @@ -762,19 +763,18 @@ fn run() -> Result<(), Box> { } Ok(()) } -# + +# #[cfg(feature = "serde")] # fn main() { # if let Err(err) = run() { # println!("{}", err); # process::exit(1); # } # } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` Running this code should show similar output as previous examples: @@ -802,7 +802,7 @@ a new `use` statement that imports `HashMap` from the standard library: ```no_run //tutorial-read-serde-03.rs -# #[cfg(feature = "serde")] { +# #[allow(dead_code)] use std::collections::HashMap; # use std::{error::Error, io, process}; @@ -810,6 +810,7 @@ use std::collections::HashMap; // record type. type Record = HashMap; +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -818,19 +819,18 @@ fn run() -> Result<(), Box> { } Ok(()) } -# + +# #[cfg(feature = "serde")] # fn main() { # if let Err(err) = run() { # println!("{}", err); # process::exit(1); # } # } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` Running this program shows similar results as before, but each record is @@ -867,10 +867,11 @@ how. Don't miss the new Serde imports! ```no_run //tutorial-read-serde-04.rs # #![allow(dead_code)] -# #[cfg(feature = "serde")] { +# #[cfg(feature = "serde")] # use std::{error::Error, io, process}; // This lets us write `#[derive(Deserialize)]`. +# #[cfg(feature = "serde")] use serde::Deserialize; // We don't need to derive `Debug` (which doesn't require Serde), but it's a @@ -878,6 +879,7 @@ use serde::Deserialize; // // Notice that the field names in this struct are NOT in the same order as // the fields in the CSV data! +# #[cfg(feature = "serde")] #[derive(Debug, Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { @@ -888,6 +890,7 @@ struct Record { state: String, } +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -899,12 +902,16 @@ fn run() -> Result<(), Box> { Ok(()) } +# #[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +# #[cfg(not(feature = "serde"))] +# fn main() { +# println!("this example requires the 'serde' feature"); # } ``` @@ -1006,12 +1013,9 @@ Let's start by running our program from the previous section: ```no_run //tutorial-read-serde-invalid-01.rs # #![allow(dead_code)] -# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; -# -# use serde::Deserialize; -# -#[derive(Debug, Deserialize)] +# #[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { latitude: f64, @@ -1021,6 +1025,7 @@ struct Record { state: String, } +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -1029,13 +1034,16 @@ fn run() -> Result<(), Box> { } Ok(()) } -# +# #[cfg(feature = "serde")] # fn main() { # if let Err(err) = run() { # println!("{}", err); # process::exit(1); # } # } +# #[cfg(not(feature = "serde"))] +# fn main() { +# println!("this example requires the 'serde' feature"); # } ``` @@ -1076,11 +1084,9 @@ to a `None` value, as shown in this next example: ```no_run //tutorial-read-serde-invalid-02.rs # #![allow(dead_code)] -# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; -# -# use serde::Deserialize; -#[derive(Debug, Deserialize)] +# #[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { latitude: f64, @@ -1090,7 +1096,7 @@ struct Record { city: String, state: String, } - +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -1099,19 +1105,17 @@ fn run() -> Result<(), Box> { } Ok(()) } -# +# #[cfg(feature = "serde")] # fn main() { # if let Err(err) = run() { # println!("{}", err); # process::exit(1); # } # } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` If you compile and run this example, then it should run to completion just @@ -1375,9 +1379,8 @@ As with reading, let's start by seeing how we can serialize a Rust tuple. ```no_run //tutorial-write-serde-01.rs -# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; -# +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut wtr = csv::Writer::from_writer(io::stdout()); @@ -1397,19 +1400,17 @@ fn run() -> Result<(), Box> { wtr.flush()?; Ok(()) } -# +# #[cfg(feature = "serde")] # fn main() { # if let Err(err) = run() { # println!("{}", err); # process::exit(1); # } # } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` Compiling and running this program gives the expected output: @@ -1450,13 +1451,11 @@ shown in the example: ```no_run //tutorial-write-serde-02.rs -# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; -use serde::Serialize; - +# #[cfg(feature = "serde")] // Note that structs can derive both Serialize and Deserialize! -#[derive(Debug, Serialize)] +#[derive(Debug, serde::Serialize)] #[serde(rename_all = "PascalCase")] struct Record<'a> { city: &'a str, @@ -1466,6 +1465,7 @@ struct Record<'a> { longitude: f64, } +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut wtr = csv::Writer::from_writer(io::stdout()); @@ -1495,18 +1495,17 @@ fn run() -> Result<(), Box> { Ok(()) } +# #[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` Compiling and running this example has the same output as last time, even @@ -1770,14 +1769,12 @@ Now here's the code: ```no_run //tutorial-pipeline-pop-01.rs -# #[cfg(feature = "serde")] { # use std::{env, error::Error, io, process}; -use serde::{Deserialize, Serialize}; - +# #[cfg(feature = "serde")] // Unlike previous examples, we derive both Deserialize and Serialize. This // means we'll be able to automatically deserialize and serialize this type. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "PascalCase")] struct Record { city: String, @@ -1787,6 +1784,7 @@ struct Record { longitude: f64, } +# #[cfg(feature = "serde")] fn run() -> Result<(), Box> { // Get the query from the positional arguments. // If one doesn't exist or isn't an integer, return an error. @@ -1822,18 +1820,17 @@ fn run() -> Result<(), Box> { Ok(()) } +# #[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` If we compile and run our program with a minimum threshold of `100000`, we @@ -2122,12 +2119,10 @@ example using Serde in a previous section: ```no_run //tutorial-perf-serde-01.rs # #![allow(dead_code)] -# #[cfg(feature = "serde")] { use std::{error::Error, io, process}; -use serde::Deserialize; - -#[derive(Debug, Deserialize)] +# #[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { country: String, @@ -2139,6 +2134,7 @@ struct Record { longitude: f64, } +# #[cfg(feature = "serde")] fn run() -> Result> { let mut rdr = csv::Reader::from_reader(io::stdin()); @@ -2152,6 +2148,7 @@ fn run() -> Result> { Ok(count) } +# #[cfg(feature = "serde")] fn main() { match run() { Ok(count) => { @@ -2163,12 +2160,10 @@ fn main() { } } } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` Now compile and run this program: @@ -2197,11 +2192,9 @@ like: ```no_run //tutorial-perf-serde-02.rs # #![allow(dead_code)] -# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; -# use serde::Deserialize; -# -#[derive(Debug, Deserialize)] +# #[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record<'a> { country: &'a str, @@ -2213,6 +2206,7 @@ struct Record<'a> { longitude: f64, } +# #[cfg(feature = "serde")] fn run() -> Result> { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::StringRecord::new(); @@ -2227,7 +2221,8 @@ fn run() -> Result> { } Ok(count) } -# + +# #[cfg(feature = "serde")] # fn main() { # match run() { # Ok(count) => { @@ -2239,12 +2234,10 @@ fn run() -> Result> { # } # } # } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` Compile and run: @@ -2289,12 +2282,10 @@ of `StringRecord`: ```no_run //tutorial-perf-serde-03.rs # #![allow(dead_code)] -# #[cfg(feature = "serde")] { # use std::{error::Error, io, process}; # -# use serde::Deserialize; -# -#[derive(Debug, Deserialize)] +# #[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record<'a> { country: &'a [u8], @@ -2306,6 +2297,7 @@ struct Record<'a> { longitude: f64, } +# #[cfg(feature = "serde")] fn run() -> Result> { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::ByteRecord::new(); @@ -2320,7 +2312,8 @@ fn run() -> Result> { } Ok(count) } -# + +# #[cfg(feature = "serde")] # fn main() { # match run() { # Ok(count) => { @@ -2332,12 +2325,10 @@ fn run() -> Result> { # } # } # } -# } -# #[cfg(not(feature = "serde"))] { +# #[cfg(not(feature = "serde"))] # fn main() { # println!("this example requires the 'serde' feature"); # } -# } ``` Compile and run: diff --git a/src/writer.rs b/src/writer.rs index bfe45782..3b736af7 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -160,11 +160,12 @@ impl WriterBuilder { /// names of a struct. /// /// ``` - /// # #[cfg(feature = "serde")] { + /// # #[allow(dead_code)] /// use std::error::Error; /// /// use csv::WriterBuilder; /// + /// # #[cfg(feature = "serde")] /// #[derive(serde::Serialize)] /// struct Row<'a> { /// city: &'a str, @@ -175,7 +176,9 @@ impl WriterBuilder { /// population: u64, /// } /// + /// # #[cfg(feature = "serde")] /// # fn main() { example().unwrap(); } + /// # #[cfg(feature = "serde")] /// fn example() -> Result<(), Box> { /// let mut wtr = WriterBuilder::new().from_writer(vec![]); /// wtr.serialize(Row { @@ -197,7 +200,8 @@ impl WriterBuilder { /// "); /// Ok(()) /// } - /// # } + /// # #[cfg(not(feature = "serde"))] + /// # fn main() {} /// ``` /// /// # Example: without headers @@ -208,11 +212,12 @@ impl WriterBuilder { /// explicitly want to both write custom headers and serialize structs. /// /// ``` - /// # #[cfg(feature = "serde")] { /// use std::error::Error; /// use csv::WriterBuilder; /// + /// # #[cfg(feature = "serde")] /// # fn main() { example().unwrap(); } + /// # #[cfg(feature = "serde")] /// fn example() -> Result<(), Box> { /// let mut wtr = WriterBuilder::new().from_writer(vec![]); /// wtr.serialize(("Boston", "United States", 4628910))?; @@ -225,7 +230,8 @@ impl WriterBuilder { /// "); /// Ok(()) /// } - /// # } + /// # #[cfg(not(feature = "serde"))] + /// # fn main() {} /// ``` pub fn has_headers(&mut self, yes: bool) -> &mut WriterBuilder { self.has_headers = yes; From df947df2857fc55186321635367f11e14beba344 Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 29 Oct 2025 13:49:41 +0100 Subject: [PATCH 4/5] Regenerated tutorials --- examples/tutorial-perf-serde-01.rs | 12 ++++++++---- examples/tutorial-perf-serde-02.rs | 12 ++++++++---- examples/tutorial-perf-serde-03.rs | 12 ++++++++---- examples/tutorial-pipeline-pop-01.rs | 12 ++++++++---- examples/tutorial-read-serde-02.rs | 8 +++++++- examples/tutorial-read-serde-03.rs | 8 +++++++- examples/tutorial-read-serde-04.rs | 10 +++++++++- examples/tutorial-read-serde-invalid-01.rs | 14 ++++++++------ examples/tutorial-read-serde-invalid-02.rs | 14 ++++++++------ examples/tutorial-write-serde-01.rs | 9 ++++++--- examples/tutorial-write-serde-02.rs | 12 ++++++++---- 11 files changed, 85 insertions(+), 38 deletions(-) diff --git a/examples/tutorial-perf-serde-01.rs b/examples/tutorial-perf-serde-01.rs index 83aaeb70..c8ce0fa0 100644 --- a/examples/tutorial-perf-serde-01.rs +++ b/examples/tutorial-perf-serde-01.rs @@ -1,10 +1,8 @@ -#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; -use serde::Deserialize; - -#[derive(Debug, Deserialize)] +#[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { country: String, @@ -16,6 +14,7 @@ struct Record { longitude: f64, } +#[cfg(feature = "serde")] fn run() -> Result> { let mut rdr = csv::Reader::from_reader(io::stdin()); @@ -29,6 +28,7 @@ fn run() -> Result> { Ok(count) } +#[cfg(feature = "serde")] fn main() { match run() { Ok(count) => { @@ -40,3 +40,7 @@ fn main() { } } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-perf-serde-02.rs b/examples/tutorial-perf-serde-02.rs index 31ad92e5..6b03eb4c 100644 --- a/examples/tutorial-perf-serde-02.rs +++ b/examples/tutorial-perf-serde-02.rs @@ -1,9 +1,7 @@ -#![cfg(feature = "serde")] #![allow(dead_code)] -use serde::Deserialize; use std::{error::Error, io, process}; - -#[derive(Debug, Deserialize)] +#[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record<'a> { country: &'a str, @@ -15,6 +13,7 @@ struct Record<'a> { longitude: f64, } +#[cfg(feature = "serde")] fn run() -> Result> { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::StringRecord::new(); @@ -30,6 +29,7 @@ fn run() -> Result> { Ok(count) } +#[cfg(feature = "serde")] fn main() { match run() { Ok(count) => { @@ -41,3 +41,7 @@ fn main() { } } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-perf-serde-03.rs b/examples/tutorial-perf-serde-03.rs index d871af02..0f216a66 100644 --- a/examples/tutorial-perf-serde-03.rs +++ b/examples/tutorial-perf-serde-03.rs @@ -1,10 +1,8 @@ -#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; -use serde::Deserialize; - -#[derive(Debug, Deserialize)] +#[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record<'a> { country: &'a [u8], @@ -16,6 +14,7 @@ struct Record<'a> { longitude: f64, } +#[cfg(feature = "serde")] fn run() -> Result> { let mut rdr = csv::Reader::from_reader(io::stdin()); let mut raw_record = csv::ByteRecord::new(); @@ -31,6 +30,7 @@ fn run() -> Result> { Ok(count) } +#[cfg(feature = "serde")] fn main() { match run() { Ok(count) => { @@ -42,3 +42,7 @@ fn main() { } } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-pipeline-pop-01.rs b/examples/tutorial-pipeline-pop-01.rs index bf6ac309..22ddd2f0 100644 --- a/examples/tutorial-pipeline-pop-01.rs +++ b/examples/tutorial-pipeline-pop-01.rs @@ -1,11 +1,9 @@ -#![cfg(feature = "serde")] use std::{env, error::Error, io, process}; -use serde::{Deserialize, Serialize}; - +#[cfg(feature = "serde")] // Unlike previous examples, we derive both Deserialize and Serialize. This // means we'll be able to automatically deserialize and serialize this type. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "PascalCase")] struct Record { city: String, @@ -15,6 +13,7 @@ struct Record { longitude: f64, } +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { // Get the query from the positional arguments. // If one doesn't exist or isn't an integer, return an error. @@ -50,9 +49,14 @@ fn run() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-read-serde-02.rs b/examples/tutorial-read-serde-02.rs index d098e1e0..431d8aa7 100644 --- a/examples/tutorial-read-serde-02.rs +++ b/examples/tutorial-read-serde-02.rs @@ -1,10 +1,11 @@ -#![cfg(feature = "serde")] +#[allow(dead_code)] use std::{error::Error, io, process}; // This introduces a type alias so that we can conveniently reference our // record type. type Record = (String, String, Option, f64, f64); +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); // Instead of creating an iterator with the `records` method, we create @@ -17,9 +18,14 @@ fn run() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-read-serde-03.rs b/examples/tutorial-read-serde-03.rs index ed352f45..e6f577d1 100644 --- a/examples/tutorial-read-serde-03.rs +++ b/examples/tutorial-read-serde-03.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "serde")] +#[allow(dead_code)] use std::collections::HashMap; use std::{error::Error, io, process}; @@ -6,6 +6,7 @@ use std::{error::Error, io, process}; // record type. type Record = HashMap; +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -15,9 +16,14 @@ fn run() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-read-serde-04.rs b/examples/tutorial-read-serde-04.rs index deb6e957..31a319c9 100644 --- a/examples/tutorial-read-serde-04.rs +++ b/examples/tutorial-read-serde-04.rs @@ -1,8 +1,9 @@ -#![cfg(feature = "serde")] #![allow(dead_code)] +#[cfg(feature = "serde")] use std::{error::Error, io, process}; // This lets us write `#[derive(Deserialize)]`. +#[cfg(feature = "serde")] use serde::Deserialize; // We don't need to derive `Debug` (which doesn't require Serde), but it's a @@ -10,6 +11,7 @@ use serde::Deserialize; // // Notice that the field names in this struct are NOT in the same order as // the fields in the CSV data! +#[cfg(feature = "serde")] #[derive(Debug, Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { @@ -20,6 +22,7 @@ struct Record { state: String, } +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -31,9 +34,14 @@ fn run() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-read-serde-invalid-01.rs b/examples/tutorial-read-serde-invalid-01.rs index 24bf7a33..6485f8fa 100644 --- a/examples/tutorial-read-serde-invalid-01.rs +++ b/examples/tutorial-read-serde-invalid-01.rs @@ -1,10 +1,7 @@ -#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; - -use serde::Deserialize; - -#[derive(Debug, Deserialize)] +#[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { latitude: f64, @@ -14,6 +11,7 @@ struct Record { state: String, } +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -22,10 +20,14 @@ fn run() -> Result<(), Box> { } Ok(()) } - +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-read-serde-invalid-02.rs b/examples/tutorial-read-serde-invalid-02.rs index 01114662..a6647259 100644 --- a/examples/tutorial-read-serde-invalid-02.rs +++ b/examples/tutorial-read-serde-invalid-02.rs @@ -1,9 +1,7 @@ -#![cfg(feature = "serde")] #![allow(dead_code)] use std::{error::Error, io, process}; - -use serde::Deserialize; -#[derive(Debug, Deserialize)] +#[cfg(feature = "serde")] +#[derive(Debug, serde::Deserialize)] #[serde(rename_all = "PascalCase")] struct Record { latitude: f64, @@ -13,7 +11,7 @@ struct Record { city: String, state: String, } - +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut rdr = csv::Reader::from_reader(io::stdin()); for result in rdr.deserialize() { @@ -22,10 +20,14 @@ fn run() -> Result<(), Box> { } Ok(()) } - +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-write-serde-01.rs b/examples/tutorial-write-serde-01.rs index bdac93aa..f65d0a4a 100644 --- a/examples/tutorial-write-serde-01.rs +++ b/examples/tutorial-write-serde-01.rs @@ -1,6 +1,5 @@ -#![cfg(feature = "serde")] use std::{error::Error, io, process}; - +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut wtr = csv::Writer::from_writer(io::stdout()); @@ -32,10 +31,14 @@ fn run() -> Result<(), Box> { wtr.flush()?; Ok(()) } - +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} diff --git a/examples/tutorial-write-serde-02.rs b/examples/tutorial-write-serde-02.rs index 65735540..62f26883 100644 --- a/examples/tutorial-write-serde-02.rs +++ b/examples/tutorial-write-serde-02.rs @@ -1,10 +1,8 @@ -#![cfg(feature = "serde")] use std::{error::Error, io, process}; -use serde::Serialize; - +#[cfg(feature = "serde")] // Note that structs can derive both Serialize and Deserialize! -#[derive(Debug, Serialize)] +#[derive(Debug, serde::Serialize)] #[serde(rename_all = "PascalCase")] struct Record<'a> { city: &'a str, @@ -14,6 +12,7 @@ struct Record<'a> { longitude: f64, } +#[cfg(feature = "serde")] fn run() -> Result<(), Box> { let mut wtr = csv::Writer::from_writer(io::stdout()); @@ -43,9 +42,14 @@ fn run() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "serde")] fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } +#[cfg(not(feature = "serde"))] +fn main() { + println!("this example requires the 'serde' feature"); +} From 7aca6b7a3c22cc43b96899266a40353933a9db5b Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 29 Oct 2025 14:02:29 +0100 Subject: [PATCH 5/5] Resolved some clippy code smells --- Cargo.toml | 5 +++++ csv-core/src/writer.rs | 8 +------- src/byte_record.rs | 7 +++++++ src/debug.rs | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e3b3e3b3..ad02fb32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,11 @@ members = ["csv-core", "csv-index"] [lib] bench = false +# Allow for needless main in doctest, as some examples are tutorials. +[lints.clippy] +needless_doctest_main = "allow" +upper_case_acronyms = "allow" + [dependencies] csv-core = { path = "csv-core", version = "0.1.11" } itoa = "1" diff --git a/csv-core/src/writer.rs b/csv-core/src/writer.rs index 011ab065..16378179 100644 --- a/csv-core/src/writer.rs +++ b/csv-core/src/writer.rs @@ -217,7 +217,7 @@ impl fmt::Debug for Writer { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] struct WriterState { /// This is set whenever we've begun writing the contents of a field, even /// if the contents are empty. We use it to avoid re-computing whether @@ -496,12 +496,6 @@ impl Default for Writer { } } -impl Default for WriterState { - fn default() -> WriterState { - WriterState { in_field: false, quoting: false, record_bytes: 0 } - } -} - /// Returns true if and only if the given input is non-numeric. pub fn is_non_numeric(input: &[u8]) -> bool { let s = match str::from_utf8(input) { diff --git a/src/byte_record.rs b/src/byte_record.rs index 63585f04..b579c864 100644 --- a/src/byte_record.rs +++ b/src/byte_record.rs @@ -592,6 +592,13 @@ pub struct Position { record: u64, } +impl Default for Position { + #[inline] + fn default() -> Position { + Position::new() + } +} + impl Position { /// Returns a new position initialized to the start value. #[inline] diff --git a/src/debug.rs b/src/debug.rs index 5001b04d..d6d1022d 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -31,7 +31,7 @@ impl<'a> core::fmt::Debug for Bytes<'a> { | '\x7f' => { write!(f, "\\x{:02x}", u32::from(ch))?; } - '\n' | '\r' | '\t' | _ => { + _ => { write!(f, "{}", ch.escape_debug())?; } }