From f1652093ce4f85e98b4aa5881fae92e03b08f1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9C=E4=B8=96=E6=A9=8B=20Du=20Shiqiao?= Date: Fri, 29 Sep 2023 11:52:26 +0900 Subject: [PATCH 1/2] use anyhow for error handling (#7) --- Cargo.toml | 3 +-- README.md | 16 ++++++++++------ examples/{anyhow.rs => json2jsonl.rs} | 14 +++++++------- examples/json2yaml.rs | 23 ----------------------- src/backend/csv.rs | 7 +++---- src/backend/json.rs | 7 +++---- src/backend/jsonlines.rs | 7 +++---- src/backend/yaml.rs | 7 +++---- src/lib.rs | 4 ++-- src/read.rs | 16 +++++++--------- src/{common.rs => types.rs} | 13 ++++++------- src/write.rs | 19 ++++++++++--------- 12 files changed, 55 insertions(+), 81 deletions(-) rename examples/{anyhow.rs => json2jsonl.rs} (56%) delete mode 100644 examples/json2yaml.rs rename src/{common.rs => types.rs} (84%) diff --git a/Cargo.toml b/Cargo.toml index 265999c..fb37611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,13 @@ categories = ["io-util", "serialization"] [lib] name = "serdeio" +doctest = false [dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml = { version = "0.9", optional = true } csv = { version = "1", optional = true } - -[dev-dependencies] anyhow = "1" [features] diff --git a/README.md b/README.md index 43e26d9..b2795f8 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,9 @@ Note that some data format like CSV and JSON Lines support only reading records The following code read a JSON file and parse it as `Vec`. Then it encodes the data into YAML format and write it to STDOUT. ```rust +use anyhow::{anyhow, Context, Result as AnyResult}; use serde::{Deserialize, Serialize}; -use serdeio::{read_records_from_file, write_records_to_writer, DataFormat}; +use serdeio::{read_record_from_file, write_records_to_writer, DataFormat}; #[derive(Debug, Deserialize, Serialize)] struct User { @@ -36,16 +37,19 @@ struct User { items: Vec, } -fn main() { +pub fn main() -> AnyResult<()> { // get input file path from argv let args: Vec = std::env::args().collect(); let input_file_path = &args[1]; - // read to memory - let users: Vec = read_records_from_file(input_file_path).unwrap(); + // read json file to memory + let users: Vec = read_record_from_file(input_file_path) + .context("Failed to read records from file")?; - // write to stdout in YAML format + // write to stdout in json lines format let writer = std::io::stdout(); - write_records_to_writer(writer, DataFormat::Yaml, &users).unwrap(); + write_records_to_writer(writer, DataFormat::JsonLines, &users).unwrap(); + + Ok(()) } ``` \ No newline at end of file diff --git a/examples/anyhow.rs b/examples/json2jsonl.rs similarity index 56% rename from examples/anyhow.rs rename to examples/json2jsonl.rs index f2dc5a7..db68a7f 100644 --- a/examples/anyhow.rs +++ b/examples/json2jsonl.rs @@ -1,27 +1,27 @@ use anyhow::{anyhow, Context, Result as AnyResult}; use serde::{Deserialize, Serialize}; -use serdeio::{read_records_from_file, write_records_to_writer, DataFormat}; +use serdeio::{read_record_from_file, write_records_to_writer, DataFormat}; #[derive(Debug, Deserialize, Serialize)] struct User { id: u32, name: String, - items: Vec, + items: Vec, } -fn main() -> AnyResult<()> { +pub fn main() -> AnyResult<()> { // get input file path from argv let args: Vec = std::env::args().collect(); let input_file_path = &args[1]; - // read to memory - let users: Vec = read_records_from_file(input_file_path) + // read json to memory + let users: Vec = read_record_from_file(input_file_path) .map_err(|e| anyhow! {e}) .context("Failed to read records from file")?; - // write to stdout in YAML format + // write to stdout in json lines format let writer = std::io::stdout(); - write_records_to_writer(writer, DataFormat::Yaml, &users).unwrap(); + write_records_to_writer(writer, DataFormat::JsonLines, &users).unwrap(); Ok(()) } diff --git a/examples/json2yaml.rs b/examples/json2yaml.rs deleted file mode 100644 index 3c383f3..0000000 --- a/examples/json2yaml.rs +++ /dev/null @@ -1,23 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use serdeio::{read_records_from_file, write_records_to_writer, DataFormat}; - -#[derive(Debug, Deserialize, Serialize)] -struct User { - id: u32, - name: String, - items: Vec, -} - -fn main() { - // get input file path from argv - let args: Vec = std::env::args().collect(); - let input_file_path = &args[1]; - - // read to memory - let users: Vec = read_records_from_file(input_file_path).unwrap(); - - // write to stdout in YAML format - let writer = std::io::stdout(); - write_records_to_writer(writer, DataFormat::Yaml, &users).unwrap(); -} diff --git a/src/backend/csv.rs b/src/backend/csv.rs index c82abd3..5de9d01 100644 --- a/src/backend/csv.rs +++ b/src/backend/csv.rs @@ -1,10 +1,9 @@ use std::io::{Read, Write}; +use anyhow::Result as AnyResult; use serde::{de::DeserializeOwned, Serialize}; -use crate::common::Result; - -pub fn read(reader: impl Read) -> Result> { +pub fn read(reader: impl Read) -> AnyResult> { let mut rdr = csv::Reader::from_reader(reader); let mut records: Vec = Vec::new(); for result in rdr.deserialize() { @@ -14,7 +13,7 @@ pub fn read(reader: impl Read) -> Result> { Ok(records) } -pub fn write(writer: impl Write, records: &[T]) -> Result<()> { +pub fn write(writer: impl Write, records: &[T]) -> AnyResult<()> { let mut wtr = csv::Writer::from_writer(writer); for record in records { wtr.serialize(record)?; diff --git a/src/backend/json.rs b/src/backend/json.rs index 760e5dc..92369b4 100644 --- a/src/backend/json.rs +++ b/src/backend/json.rs @@ -1,13 +1,12 @@ use std::io::{Read, Write}; +use anyhow::Result as AnyResult; use serde::{de::DeserializeOwned, Serialize}; -use crate::common::Result; - -pub fn read(reader: impl Read) -> Result { +pub fn read(reader: impl Read) -> AnyResult { serde_json::from_reader(reader).map_err(|e| e.into()) } -pub fn write(writer: impl Write, record: &T) -> Result<()> { +pub fn write(writer: impl Write, record: &T) -> AnyResult<()> { serde_json::to_writer(writer, record).map_err(|e| e.into()) } diff --git a/src/backend/jsonlines.rs b/src/backend/jsonlines.rs index f08f903..e99744d 100644 --- a/src/backend/jsonlines.rs +++ b/src/backend/jsonlines.rs @@ -1,10 +1,9 @@ use std::io::{BufRead, BufReader, BufWriter, Read, Write}; use serde::{de::DeserializeOwned, Serialize}; +use anyhow::Result as AnyResult; -use crate::common::Result; - -pub fn read(reader: impl Read) -> Result> { +pub fn read(reader: impl Read) -> AnyResult> { let reader = BufReader::new(reader); let mut records: Vec = Vec::new(); for line in reader.lines() { @@ -15,7 +14,7 @@ pub fn read(reader: impl Read) -> Result> { Ok(records) } -pub fn write(writer: impl Write, records: &[T]) -> Result<()> { +pub fn write(writer: impl Write, records: &[T]) -> AnyResult<()> { let mut writer = BufWriter::new(writer); for record in records { let line = serde_json::to_string(record)?; diff --git a/src/backend/yaml.rs b/src/backend/yaml.rs index 465f50f..849d208 100644 --- a/src/backend/yaml.rs +++ b/src/backend/yaml.rs @@ -1,13 +1,12 @@ use std::io::{Read, Write}; +use anyhow::Result as AnyResult; use serde::{de::DeserializeOwned, Serialize}; -use crate::common::Result; - -pub fn read(reader: impl Read) -> Result { +pub fn read(reader: impl Read) -> AnyResult { serde_yaml::from_reader(reader).map_err(|e| e.into()) } -pub fn write(writer: impl Write, record: &T) -> Result<()> { +pub fn write(writer: impl Write, record: &T) -> AnyResult<()> { serde_yaml::to_writer(writer, record).map_err(|e| e.into()) } diff --git a/src/lib.rs b/src/lib.rs index 9e2f79b..7d3f7c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,11 @@ #![doc = include_str!("../README.md")] pub(crate) mod backend; -pub(crate) mod common; +pub(crate) mod types; pub(crate) mod read; pub(crate) mod write; -pub use common::DataFormat; +pub use types::DataFormat; pub use read::{ read_record_from_file, read_record_from_reader, read_records_from_file, read_records_from_reader, diff --git a/src/read.rs b/src/read.rs index 5faf14c..0296b3a 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1,28 +1,26 @@ use std::{fs::File, io::Read, path::Path}; +use anyhow::{anyhow, Result as AnyResult}; use serde::de::DeserializeOwned; -use crate::{ - backend, - common::{DataFormat, Result}, -}; +use crate::{backend, types::DataFormat}; pub fn read_record_from_reader( reader: impl Read, data_format: DataFormat, -) -> Result { +) -> AnyResult { match data_format { DataFormat::Json => backend::json::read(reader), #[cfg(feature = "yaml")] DataFormat::Yaml => backend::yaml::read(reader), - _ => Err(format!("Unsupported file format: {}", data_format).into()), + _ => Err(anyhow!("Unsupported file format: {}", data_format)), } } pub fn read_records_from_reader( reader: impl Read, data_format: DataFormat, -) -> Result> { +) -> AnyResult> { match data_format { DataFormat::Json => backend::json::read(reader), DataFormat::JsonLines => backend::jsonlines::read(reader), @@ -33,13 +31,13 @@ pub fn read_records_from_reader( } } -pub fn read_record_from_file(path: impl AsRef) -> Result { +pub fn read_record_from_file(path: impl AsRef) -> AnyResult { let data_format = DataFormat::try_from(path.as_ref())?; let file = File::open(path)?; read_record_from_reader(file, data_format) } -pub fn read_records_from_file(path: impl AsRef) -> Result> { +pub fn read_records_from_file(path: impl AsRef) -> AnyResult> { let data_format = DataFormat::try_from(path.as_ref())?; let file = File::open(path)?; read_records_from_reader(file, data_format) diff --git a/src/common.rs b/src/types.rs similarity index 84% rename from src/common.rs rename to src/types.rs index 082b734..025ef37 100644 --- a/src/common.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use anyhow::{anyhow, Error as AnyError}; use std::{fmt::Display, path::Path}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -11,7 +12,7 @@ pub enum DataFormat { } impl TryFrom<&str> for DataFormat { - type Error = String; + type Error = AnyError; fn try_from(value: &str) -> std::result::Result { match value.trim().to_lowercase().as_str() { @@ -21,19 +22,19 @@ impl TryFrom<&str> for DataFormat { "csv" => Ok(DataFormat::Csv), #[cfg(feature = "yaml")] "yaml" | "yml" => Ok(DataFormat::Yaml), - _ => Err(format!("Unknown data format: {}", value)), + _ => Err(anyhow!("Unknown data format: {}", value)), } } } impl TryFrom<&Path> for DataFormat { - type Error = String; + type Error = AnyError; fn try_from(value: &Path) -> std::result::Result { let ext = value .extension() - .ok_or_else(|| format!("No extension found for file: {}", value.display())) - .and_then(|v| v.to_str().ok_or("Invalid extension".to_owned()))?; + .ok_or_else(|| anyhow!("No extension found for file: {}", value.display())) + .and_then(|v| v.to_str().ok_or(anyhow!("Invalid extension")))?; Self::try_from(ext) } } @@ -51,8 +52,6 @@ impl Display for DataFormat { } } -pub(crate) type Result = std::result::Result>; - #[cfg(test)] mod test { #[test] diff --git a/src/write.rs b/src/write.rs index 000e07b..6c05247 100644 --- a/src/write.rs +++ b/src/write.rs @@ -1,22 +1,20 @@ use std::{fs::File, io::Write, path::Path}; +use anyhow::{anyhow, Result as AnyResult}; use serde::Serialize; -use crate::{ - backend, - common::{DataFormat, Result}, -}; +use crate::{backend, types::DataFormat}; pub fn write_record_to_writer( writer: impl Write, data_format: DataFormat, record: &T, -) -> Result<()> { +) -> AnyResult<()> { match data_format { DataFormat::Json => backend::json::write(writer, record), #[cfg(feature = "yaml")] DataFormat::Yaml => backend::yaml::write(writer, record), - _ => Err(format!("Unsupported file format: {}", data_format).into()), + _ => Err(anyhow!("Unsupported file format: {}", data_format)), } } @@ -24,7 +22,7 @@ pub fn write_records_to_writer( writer: impl Write, data_format: DataFormat, records: &Vec, -) -> Result<()> { +) -> AnyResult<()> { match data_format { DataFormat::Json => backend::json::write(writer, records), DataFormat::JsonLines => backend::jsonlines::write(writer, records), @@ -35,13 +33,16 @@ pub fn write_records_to_writer( } } -pub fn write_record_to_file(path: impl AsRef, records: &T) -> Result<()> { +pub fn write_record_to_file(path: impl AsRef, records: &T) -> AnyResult<()> { let data_format = DataFormat::try_from(path.as_ref())?; let file = File::create(path)?; write_record_to_writer(file, data_format, records) } -pub fn write_records_to_file(path: impl AsRef, records: &Vec) -> Result<()> { +pub fn write_records_to_file( + path: impl AsRef, + records: &Vec, +) -> AnyResult<()> { let data_format = DataFormat::try_from(path.as_ref())?; let file = File::create(path)?; write_records_to_writer(file, data_format, records) From b398004f63d84b263f66592a34e2724f13b1e042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9C=20=E4=B8=96=E6=A9=8B=20Du=20Shiqiao?= Date: Fri, 29 Sep 2023 11:53:08 +0900 Subject: [PATCH 2/2] bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fb37611..3b19ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serdeio" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" authors = ["Du Shiqiao "]