### Installation using Cargo Source: https://github.com/denisgorbachev/errgonomic/blob/main/README.md Shows the command to add the errgonomic crate to your project's dependencies using Cargo. ```shell cargo add errgonomic ``` -------------------------------- ### Parse Config Example Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Compares two functions for parsing configuration files. The 'good' version uses `handle!` macro to create more informative error types, including the original error source and context like the file path. This approach is more verbose but provides better debugging information. ```rust mod get_root_error; mod partition_result; pub use get_root_error::*; pub use partition_result::*; cfg_if::cfg_if! { if #[cfg(feature = "std")] { mod writeln_error; mod write_to_named_temp_file; mod exit; pub use writeln_error::*; pub use write_to_named_temp_file::*; pub use exit::*; } } cfg_if::cfg_if! { if #[cfg(all(feature = "process"))] { mod render_command; pub use render_command::*; } } ``` ```rust # #[cfg(feature = "std")] # { # use std::io; # use std::fs::read_to_string; # use std::path::{Path, PathBuf}; # use serde::{Deserialize, Serialize}; # use serde_json::from_str; # use thiserror::Error; # use errgonomic::handle; # #[derive(Serialize, Deserialize)] struct Config {/* some fields */} // bad: doesn't return the path to config (the user won't be able to fix it) fn parse_config_v1(path: PathBuf) -> io::Result { let contents = read_to_string(&path)?; let config = from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; Ok(config) } // good: returns the path to config & the underlying deserialization error (the user will be able fix it) fn parse_config_v2(path: PathBuf) -> Result { use ParseConfigError::*; let contents = handle!(read_to_string(&path), ReadToStringFailed, path); let config = handle!(from_str(&contents), DeserializeFailed, path, contents); Ok(config) } #[derive(Error, Debug)] enum ParseConfigError { #[error("failed to read file to string: '{path}'")] ReadToStringFailed { path: PathBuf, source: std::io::Error }, #[error("failed to parse the file contents into config: '{path}'")] DeserializeFailed { path: PathBuf, contents: String, source: serde_json::Error } } # } ``` -------------------------------- ### Rust Import without Disambiguation Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Provides an example where prefixes are not necessary for disambiguation due to unique trait names within the module. This leads to cleaner import statements. ```rust use clap::ValueEnum; #[derive(ValueEnum, From, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Eq, PartialEq, Hash, Clone, Copy, Debug)] pub enum Side { Buy, Sell, } ``` -------------------------------- ### Struct with Getters and Copy Annotation Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Example of a struct deriving Getters, where fields implementing Copy have the #[getter(copy)] annotation. This ensures efficient copying of primitive types. ```rust #[derive(Getters, Into, Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] pub struct User { username: String, #[getter(copy)] age: u64, } ``` -------------------------------- ### Parse Numbers with Iterator Pipeline and Error Handling Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md This is a good example of using an iterator pipeline with fallible mapping and proper error handling using `errgonomic`. It efficiently parses numbers and aggregates errors. ```Rust use errgonomic::{handle_iter, ErrVec}; use core::num::ParseIntError; use thiserror::Error; // Good: iterator pipeline with fallible mapping + correct error handling pub fn parse_numbers(inputs: impl IntoIterator>) -> Result, ParseNumbersError> { use ParseNumbersError::*; let iter = inputs.into_iter().map(|s| s.as_ref().trim().parse::()); Ok(handle_iter!(iter, InvalidInput)) } #[derive(Error, Debug)] pub enum ParseNumbersError { #[error("failed to parse {len} numbers", len = source.len())] InvalidInput { source: ErrVec }, } ``` -------------------------------- ### Get Root Source Error Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Retrieves the deepest source error in an error chain. This function iteratively calls the `source()` method on errors until no further source can be found. ```rust use core::error::Error; /// Returns the deepest source error in the error chain (the root cause). pub fn get_root_source(error: &dyn Error) -> &dyn Error { let mut source = error; while let Some(source_new) = source.source() { source = source_new; } source } ``` -------------------------------- ### Rust Module Declaration and Re-export Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Demonstrates how to declare a module and re-export its items for crate-root access. Place mod and pub use declarations at the end of the file. ```rust fn foo() {} mod my_module_name; pub use my_module_name::*; ``` -------------------------------- ### Use handle_opt! instead of ? Source: https://github.com/denisgorbachev/errgonomic/blob/main/internal/guidelines.md Use `handle_opt!` for unwrapping `Option` types, similar to how `handle!` is used for `Result`. ```rust use errgonomic::handle_opt; fn main() { let option: Option = Some(5); let value = handle_opt!(option); println!("{}", value); } ``` -------------------------------- ### Rust Import with Disambiguation Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Illustrates using prefixes for disambiguation when importing traits from different crates with potentially overlapping names. This ensures clarity in the module. ```rust use clap::ValueEnum; use serde::{Deserialize, Serialize}; #[derive(ValueEnum, Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy, Debug)] pub enum Side { Buy, Sell, } ``` -------------------------------- ### Function to Get Username from State Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Retrieves the username from the application state, which is protected by an `Arc`. This function is designed to handle potential errors during state access or username retrieval. ```rust fn get_username(state: Arc>) -> Result { ``` -------------------------------- ### Use handle_opt! instead of Option::ok_or Source: https://github.com/denisgorbachev/errgonomic/blob/main/internal/guidelines.md The `handle_opt!` macro provides a more concise way to convert an `Option` to a `Result` compared to `Option::ok_or`. ```rust use errgonomic::handle_opt; #[derive(Debug)] struct MyError; fn main() { let option: Option = Some(5); let result = handle_opt!(option, MyError); println!("{:?}", result); } ``` -------------------------------- ### Run PrintNameCommand Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Executes the PrintNameCommand, handling potential errors during configuration parsing. This demonstrates the use of the `handle!` macro. ```rust async fn run(self) -> Result<(), PrintNameCommandError> { use PrintNameCommandError::*; let Self { dir, format, } = self; let config = handle!(parse_config(&dir, format).await, ParseConfigFailed); println!("{}", config.name); Ok(()) } ``` -------------------------------- ### Ergonomic Error Handling with `handle` Macro Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Compares two functions for parsing configuration files: `parse_config_v1` (less ergonomic) and `parse_config_v2` (more ergonomic using the `handle` macro). The `v2` version provides detailed error information including the path and underlying error, aiding in debugging and recovery. This snippet requires `serde`, `serde_json`, `thiserror`, and `errgonomic` crates. ```rust # #[cfg(feature = "std")] # { # use std::io; # use std::fs::read_to_string; # use std::path::{Path, PathBuf}; # use serde::{Deserialize, Serialize}; # use serde_json::from_str; # use thiserror::Error; # use errgonomic::handle; # #[derive(Serialize, Deserialize)] struct Config {/* some fields */} // bad: doesn't return the path to config (the user won't be able to fix it) fn parse_config_v1(path: PathBuf) -> io::Result { let contents = read_to_string(&path)?; let config = from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)); Ok(config) } // good: returns the path to config & the underlying deserialization error (the user will be able fix it) fn parse_config_v2(path: PathBuf) -> Result { use ParseConfigError::*; let contents = handle!(read_to_string(&path), ReadToStringFailed, path); let config = handle!(from_str(&contents), DeserializeFailed, path, contents); Ok(config) } #[derive(Error, Debug)] enum ParseConfigError { #[error("failed to read file to string: '{path}'")] ReadToStringFailed { path: PathBuf, source: std::io::Error }, #[error("failed to parse the file contents into config: '{path}'")] DeserializeFailed { path: PathBuf, contents: String, source: serde_json::Error } } # } ``` -------------------------------- ### Main function with exit_result for debugging Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Demonstrates how to use the `exit_result` macro in the `main` function to display detailed error information for debugging purposes. This helps in understanding the root cause of errors by providing an error trace. ```rust # #[cfg(feature = "std")] # { # use errgonomic::exit_result; # use thiserror::Error; # use std::process::ExitCode; # # #[derive(Error, Debug)] # enum Err {} # # fn run() -> Result { Ok(ExitCode::SUCCESS) } # pub fn main() -> ExitCode { exit_result(run()) } # } ``` -------------------------------- ### Rust Direct Import from Crate Root Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Shows the preferred way to import items defined within the current crate. Use direct imports from the crate root for clarity. ```rust use crate::foo; ``` -------------------------------- ### Rust: Struct Conversion with Potential Errors Source: https://github.com/denisgorbachev/errgonomic/blob/main/internal/design/general.md Demonstrates converting a raw struct to a structured one, highlighting how errors during field conversion can make the entire struct unavailable. This pattern is useful when intermediate conversions might fail. ```rust struct MarketRaw { slug: String, question_id: String, } struct Market { slug: String, question_id: QuestionId } impl TryFrom for Market { type Error = (); fn try_from(market_raw: MarketRaw) -> Result { let MarketRaw { slug, question_id } = market_raw; // if an error occurs while converting question_id from String to QuestionId, the `market` will already be unavailable // some other fields may have already been converted, too todo!() } } ``` -------------------------------- ### Parse Config Function Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Parses configuration from a file, handling errors related to file reading and deserialization. Demonstrates `handle!` for file I/O and `serde_json`/`toml` deserialization. ```rust async fn parse_config(dir: &Path, format: Format) -> Result { use Format::*; use ParseConfigError::*; let path_buf = dir.join("config.json"); let contents = handle!(read_to_string(&path_buf).await, ReadFileFailed, path: path_buf); match format { Json => { let config = handle!(serde_json::de::from_str(&contents), DeserializeFromJson, path: path_buf, contents); Ok(config) } Toml => { let config = handle!(toml::de::from_str(&contents), DeserializeFromToml, path: path_buf, contents); Ok(config) } } } ``` -------------------------------- ### Rust Option/Result Mapping (Good) Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Prefer using `.map()` for transforming values within `Option` or `Result` types. This is more concise than using a `match` statement for simple transformations. ```rust use core::str::FromStr; use core::num::ParseIntError; impl FromStr for UserId { type Err = ParseIntError; fn from_str(s: &str) -> Result { s.parse::().map(Self::new) } } ``` -------------------------------- ### Async File Reference Reading with Error Handling Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Asynchronously reads the content of a file using a reference to its path. It handles potential errors such as read failures or empty files using the `handle!` and `handle_bool!` macros. This is efficient when the path is already available. ```rust async fn check_file_ref(path: &PathBuf) -> Result { use CheckFileRefError::*; let content = handle!(read_to_string(&path).await, ReadToStringFailed); handle_bool!(content.is_empty(), FileIsEmpty); Ok(content) } ``` -------------------------------- ### Function Accepting Impl Into for String Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md This function showcases good practice by accepting `impl Into`, allowing call sites to pass various types that can be converted into a String without explicit `.into()` calls. ```Rust pub fn foo(input: impl Into) { let input = input.into(); // do something } ``` -------------------------------- ### Market Builder Pattern Source: https://github.com/denisgorbachev/errgonomic/blob/main/specs/implement-fallible-groups-example.md Constructs a market object using a fluent builder pattern, setting various market-specific properties. ```rust .active(active) .closed(closed) .archived(archived) .accepting_orders(accepting_orders) .maybe_accepting_order_timestamp(accepting_order_timestamp) .minimum_order_size(minimum_order_size) .minimum_tick_size(minimum_tick_size) .maybe_condition_id(condition_id) .maybe_question_id(question_id) .question(question) .description(description) .market_slug(market_slug) .maybe_end_date_iso(end_date_iso) .maybe_game_start_time(game_start_time) .seconds_delay(seconds_delay) .maybe_fpmm(fpmm) .maker_base_fee(maker_base_fee) .taker_base_fee(taker_base_fee) .notifications_enabled(notifications_enabled) .neg_risk(neg_risk) .maybe_neg_risk_market_id(neg_risk_market_id) .maybe_neg_risk_request_id(neg_risk_request_id) .icon(icon) .image(image) .rewards(rewards) .is_50_50_outcome(is_50_50_outcome) .tokens(tokens) .tags(tags) .build() } } ``` -------------------------------- ### Market Response Conversion with Fallible Handling Source: https://github.com/denisgorbachev/errgonomic/blob/main/specs/implement-fallible-groups-example.md Converts a market response, handling potential errors during timestamp and token conversions. It returns a successful market structure or a fallible one if conversions fail. ```rust impl From for MarketResponse { fn from(market_response: ClobMarketResponsePreciseFallible) -> Self { let ClobMarketResponsePreciseFallible { question, description, market_slug, icon, image, condition_id, question_id, active, closed, archived, enable_order_book, accepting_orders, accepting_order_timestamp, minimum_order_size, minimum_tick_size, end_date_iso, game_start_time, seconds_delay, fpmm, maker_base_fee, taker_base_fee, rewards, tokens, neg_risk, neg_risk_market_id, neg_risk_request_id, is_50_50_outcome, notifications_enabled, tags, } = market_response; let rewards = rewards.into(); let accepting_order_timestamp = accepting_order_timestamp .map(from_chrono_date_time) .transpose(); let end_date_iso = end_date_iso.map(from_chrono_date_time).transpose(); let game_start_time = game_start_time.map(from_chrono_date_time).transpose(); let seconds_delay = DurationPositiveSeconds::new(seconds_delay); let tokens = Tokens::try_from(tokens); match (accepting_order_timestamp, end_date_iso, game_start_time, tokens) { (Ok(accepting_order_timestamp), Ok(end_date_iso), Ok(game_start_time), Ok(tokens)) => Ok(Self { question, description, market_slug, icon, image, condition_id, question_id, active, closed, archived, enable_order_book, accepting_orders, accepting_order_timestamp, minimum_order_size, minimum_tick_size, end_date_iso, game_start_time, seconds_delay, fpmm, maker_base_fee, taker_base_fee, rewards, tokens, neg_risk, neg_risk_market_id, neg_risk_request_id, is_50_50_outcome, notifications_enabled, tags, }), (accepting_order_timestamp, end_date_iso, game_start_time, tokens) => Err(ClobMarketResponsePreciseFallible { question, description, market_slug, icon, image, condition_id, question_id, active, closed, archived, enable_order_book, accepting_orders, accepting_order_timestamp, minimum_order_size, minimum_tick_size, end_date_iso, game_start_time, seconds_delay, fpmm, maker_base_fee, taker_base_fee, rewards, tokens, neg_risk, neg_risk_market_id, neg_risk_request_id, is_50_50_outcome, notifications_enabled, tags, }), } } } ``` -------------------------------- ### TryFrom Implementation with Consolidated Error Handling Source: https://github.com/denisgorbachev/errgonomic/blob/main/internal/guidelines.md This snippet shows how to implement `TryFrom` for a type, consolidating potential errors from multiple fallible operations into a single error enum. It uses pattern matching on results to return a unified error type that includes fields for all intermediate results and conditions. ```rust #[derive(Getters, Clone, Debug)] pub struct Human { name: String, #[getter(copy)] age: u32, } #[derive(Getters, Clone, Debug)] pub struct Adult { name: NonEmptyString, #[getter(copy)] age: u32, } impl TryFrom for Adult { type Error = TryFromHumanForAdultError; fn try_from(input: Human) -> Result { use TryFromHumanForAdultError::*; let Human { name, age, } = input; let name_result = NonEmptyString::try_from(name); let is_adult = age > 18; match (name_result, is_adult) { (Ok(name), true) => Ok(Self { name, age, }), (name_result, is_adult) => Err(ConversionFailed { name_result, age, is_adult, }), } } } #[derive(Error, Debug)] pub enum TryFromHumanForAdultError { #[error("failed to convert human to adult")] ConversionFailed { name_result: Result, age: u32, is_adult: bool }, } ``` -------------------------------- ### Improving Debugging with `exit_result` Source: https://github.com/denisgorbachev/errgonomic/blob/main/README.md Demonstrates how to use the `exit_result` function in `main` to display a detailed error trace upon program exit. This function helps in understanding the root cause of errors by presenting a hierarchical view of failures. ```rust pub fn main() -> ExitCode { exit_result(run()) } ``` -------------------------------- ### Function Accepting String (Bad Practice) Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md This function is considered bad practice because it requires the caller to explicitly convert their input to a `String` using `.into()`, which can be less convenient. ```Rust /// This is bad because the callsite may have to call .into() when passing the input argument pub fn foo(input: String) {} ``` -------------------------------- ### Errgonomic Cargo.toml Configuration Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Defines the package metadata, dependencies, and features for the Errgonomic Rust project. Includes optional features like 'futures', 'shlex', and 'tempfile'. ```toml [package] name = "errgonomic" version = "0.5.1" edition = "2024" rust-version = "1.85.0" description = "Macros for ergonomic error handling with thiserror" license = "Apache-2.0 OR MIT" homepage = "https://github.com/DenisGorbachev/errgonomic" repository = "https://github.com/DenisGorbachev/errgonomic" readme = "README.md" keywords = ["error-handling", "utils", "macros"] categories = ["rust-patterns", "development-tools", "development-tools::debugging", "no-std"] exclude = [ ".*", "*.local.*", "doc/dev", "specs", "AGENTS.ts", "README.ts", "AGENTS*.md", "CLAUDE*.md", "deno.lock", "deno.json", "commitlint.config.mjs", "fnox.toml", "lefthook.yml", "mise.toml", "rumdl.toml", "rustfmt.toml", ".yolobox" ] # "internal" dir should be included [package.metadata.details] title = "Errgonomic" tagline = "" summary = "" announcement = "" readme = { generate = true } [package.metadata.docs.rs] cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] [dependencies] cfg-if = { version = "1" } utures = { version = "0.3.31", optional = true } shlex = { version = "1.3.0", optional = true } tempfile = { version = "3", optional = true } thiserror = { version = "2", default-features = false } [dev-dependencies] futures = "0.3" pretty_assertions = "1.4.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.39", features = ["macros", "fs", "net", "rt", "rt-multi-thread"] } toml = "0.9" [features] default = ["std"] std = ["tempfile", "thiserror/std"] process = ["std", "shlex"] ``` -------------------------------- ### Async File Reading with Error Handling Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Reads multiple files asynchronously and aggregates potential errors using `join_all` and a custom macro `handle_iter_of_refs!`. This is useful for batch file operations where individual file errors should be collected. ```rust async fn read_files_ref(paths: Vec) -> Result, ReadFilesRefError> { use ReadFilesRefError::*; let iter = paths.iter().map(check_file_ref); let results = join_all(iter).await; let items = paths.into_iter().map(PathBufDisplay::from); let (outputs, _items) = handle_iter_of_refs!(results.into_iter(), items, CheckFilesRefFailed); Ok(outputs) } ``` -------------------------------- ### Errgonomic fnox.toml Configuration Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Configures providers for the fnox tool, specifying a keychain and a password-store with a defined prefix. Sets the 'if_missing' behavior to 'error'. ```toml #:schema https://fnox.jdx.dev/schema.json if_missing = "error" [providers] keychain = { type = "keychain", service = "rust-public-lib-template" } pass = { type = "password-store", prefix = "rust-public-lib-template/" } ``` -------------------------------- ### Async File Content Reading with Error Handling Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Asynchronously reads the content of a file into a string and handles potential errors like read failures or empty files using the `handle!` and `handle_bool!` macros. This is suitable for processing individual file contents. ```rust async fn check_file(path: PathBuf) -> Result { use CheckFileError::*; let content = handle!(read_to_string(&path).await, ReadToStringFailed, path); handle_bool!(content.is_empty(), FileIsEmpty, path); Ok(content) } ``` -------------------------------- ### Use handle! instead of ? Source: https://github.com/denisgorbachev/errgonomic/blob/main/internal/guidelines.md Prefer the `handle!` macro over the `?` operator for unwrapping `Result` types to ensure consistent error handling. ```rust use errgonomic::handle; fn main() { let result: Result = Ok(5); let value = handle!(result); println!("{}", value); } ``` -------------------------------- ### Precise Market Response Structure Source: https://github.com/denisgorbachev/errgonomic/blob/main/specs/implement-fallible-groups-example.md Defines the `ClobMarketResponsePrecise` struct for storing market data with precise types. Includes methods for checking tradeability and identifying bogus or missing ID markets. ```rust use crate::{Amount, ConditionId, ConvertVecTokenRawToTokensError, DurationPositiveSeconds, EventId, QuestionId, Rewards, RkyvDecimal, RkyvOffsetDateTime, TokenId, Tokens, into_chrono_date_time}; use alloy_primitives::Address; use derive_more::{From, Into}; use polymarket_client_sdk::clob::types::response::{MarketResponse, Rewards as RewardsRaw, Token as TokenRaw}; use rkyv::with::Map; use thiserror::Error; use time::OffsetDateTime; #[derive(From, Into, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, PartialEq, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct ClobMarketResponsePrecise { pub question: String, pub description: String, /// `market_slug` is unique according to check in [`crate::CacheDownloadCommand`] pub market_slug: String, pub icon: String, pub image: String, pub condition_id: Option, pub question_id: Option, pub active: bool, pub closed: bool, pub archived: bool, pub enable_order_book: bool, pub accepting_orders: bool, #[rkyv(with = Map)] pub accepting_order_timestamp: Option, #[rkyv(with = RkyvDecimal)] #[serde(with = "rust_decimal::serde::str")] pub minimum_order_size: Amount, #[rkyv(with = RkyvDecimal)] #[serde(with = "rust_decimal::serde::str")] pub minimum_tick_size: Amount, #[rkyv(with = Map)] pub end_date_iso: Option, #[rkyv(with = Map)] pub game_start_time: Option, pub seconds_delay: DurationPositiveSeconds, pub fpmm: Option
, #[rkyv(with = RkyvDecimal)] #[serde(with = "rust_decimal::serde::str")] pub maker_base_fee: Amount, #[rkyv(with = RkyvDecimal)] #[serde(with = "rust_decimal::serde::str")] pub taker_base_fee: Amount, pub rewards: Rewards, pub tokens: Tokens, pub neg_risk: bool, pub neg_risk_market_id: Option, pub neg_risk_request_id: Option, pub is_50_50_outcome: bool, pub notifications_enabled: bool, pub tags: Vec, } impl ClobMarketResponsePrecise { pub fn is_tradeable(&self) -> bool { self.active && !self.closed && !self.archived && self.accepting_orders && self.enable_order_book } pub fn is_skipped(&self) -> bool { self.is_bogus() || self.is_missing_ids() } /// Only those two market ids have `m.question_id.is_none() != m.condition_id.is_none()` /// These markets are old (2021 and 2022) and `m.closed == true` pub fn is_bogus(&self) -> bool { self.market_slug == "dodgers" || self.market_slug == "will-it-snow-in-new-yorks-central-park-on-new-years-eve-dec-31" } pub fn is_missing_ids(&self) -> bool { self.condition_id.is_none() && self.question_id.is_none() } pub fn token_ids_tuple(&self) -> (TokenId, TokenId) { self.tokens.token_ids_tuple() } pub fn token_ids_array(&self) -> [TokenId; 2] { self.tokens.token_ids_array() } } ``` -------------------------------- ### writeln_error_to_formatter Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Recursively writes a human-readable error trace to a provided `Formatter`. It prints the main error and then its source, followed by the source's source, and so on. ```APIDOC ## writeln_error_to_formatter ### Description Writes a human-readable error trace to the provided formatter. ### Signature ```rust pub fn writeln_error_to_formatter(error: &E, f: &mut Formatter<'_>) -> core::fmt::Result ``` ### Parameters * `error`: A reference to an error implementing the `Error` trait. * `f`: A mutable reference to a `Formatter` to write the error trace to. ``` -------------------------------- ### Ergonomic Error Handling with `handle!` Macro Source: https://github.com/denisgorbachev/errgonomic/blob/main/README.md Compares traditional error handling with the `handle!` macro for more detailed error reporting. The `handle!` macro helps in returning specific error types with context, such as file paths and underlying errors, aiding in debugging and recovery. ```rust #[derive(Serialize, Deserialize)] struct Config {/* some fields */} // bad: doesn't return the path to config (the user won't be able to fix it) fn parse_config_v1(path: PathBuf) -> io::Result { let contents = read_to_string(&path)?; let config = from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)); Ok(config) } // good: returns the path to config & the underlying deserialization error (the user will be able fix it) fn parse_config_v2(path: PathBuf) -> Result { use ParseConfigError::*; let contents = handle!(read_to_string(&path), ReadToStringFailed, path); let config = handle!(from_str(&contents), DeserializeFailed, path, contents); Ok(config) } #[derive(Error, Debug)] enum ParseConfigError { #[error("failed to read file to string: '{path}'")] ReadToStringFailed { path: PathBuf, source: std::io::Error }, #[error("failed to parse the file contents into config: '{path}'")] DeserializeFailed { path: PathBuf, contents: String, source: serde_json::Error } } ``` -------------------------------- ### Write Buffer to Named Temporary File Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Writes a byte buffer to a named temporary file and then persists it to disk. It returns the `File` handle and the `PathBuf` of the persisted file. Errors during creation, writing, or persistence are mapped to specific variants of `WriteToNamedTempFileError`. ```rust use crate::{handle, map_err}; use std::fs::File; use std::io; use std::io::Write; use std::path::PathBuf; use tempfile::{NamedTempFile, PersistError}; use thiserror::Error; /// Writes the provided buffer to a named temporary file and persists it to disk. /// /// Returns the persisted file handle and its path. pub fn write_to_named_temp_file(buf: &[u8]) -> Result<(File, PathBuf), WriteToNamedTempFileError> { use WriteToNamedTempFileError::*; let mut temp = handle!(NamedTempFile::new(), CreateTempFileFailed); handle!(temp.write_all(buf), WriteFailed); map_err!(temp.keep(), KeepFailed) } /// Errors returned by [`write_to_named_temp_file`]. #[derive(Error, Debug)] pub enum WriteToNamedTempFileError { /// Failed to create a temporary file. #[error("failed to create a temporary file")] CreateTempFileFailed { source: io::Error }, /// Failed to write the buffer into the temporary file. #[error("failed to write to a temporary file")] WriteFailed { source: io::Error }, /// Failed to persist the temporary file to its final path. #[error("failed to persist the temporary file")] KeepFailed { source: PersistError }, } ``` -------------------------------- ### Use handle_into_iter! for collections Source: https://github.com/denisgorbachev/errgonomic/blob/main/internal/guidelines.md Handle errors in collections implementing `IntoIterator` with `handle_into_iter!`. ```rust use errgonomic::handle_into_iter; #[derive(Debug)] struct MyError; fn main() { let data: Vec> = vec![Ok(1), Ok(2), Err(MyError)]; let collected = handle_into_iter!(data.into_iter()); // Note: This example is illustrative. `handle_into_iter!` typically returns a Result containing a Vec. } ``` -------------------------------- ### Rust Function with Trait Bounds (Good) Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Use `impl AsRef` or `impl AsMut` for function parameters to accept types that can be converted into a reference. This provides flexibility at the call site. ```rust pub fn bar(input: &mut impl AsMut) { let input = input.as_mut(); // do something } pub fn baz(input: &impl AsRef) { let input = input.as_ref(); // do something } ``` -------------------------------- ### DisplayAsDebug Wrapper for Rust Types Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Wraps a type to use its Debug implementation when Display is requested. Useful for types that have a more readable Debug output than Display. ```rust use core::fmt::{Debug, Display, Formatter}; #[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Debug)] pub struct DisplayAsDebug( pub T, ); impl Display for DisplayAsDebug { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { Debug::fmt(&self.0, f) } } impl From for DisplayAsDebug { fn from(value: T) -> Self { Self(value) } } ``` -------------------------------- ### handle_opt_take! Macro for Option with Take Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md This macro returns an error if the option contains a `Some` variant, utilizing `Option::take`. It requires a `$some_value` argument to capture the value from `Some`. ```rust /// This macro is an opposite of [`handle_opt!`](crate::handle_opt) - it returns an error if the option contains a `Some` variant. /// /// Note that this macro calls [`Option::take`], which will leave a `None` if the option was `Some(value)`. /// Note that this macro has a mandatory argument `$some_value` (used in `if let Some($some_value) = $option.take()`), which will also be passed to the error enum variant. #[macro_export] macro_rules! handle_opt_take { ($option:expr, $variant:ident, $some_value:ident$(,)? $($arg:ident$(: $value:expr)?),*) => { if let Some($some_value) = $option.take() { return Err($variant { $some_value: $some_value.into(), $($arg: $crate::_into!($arg$(: $value)?)),* }) } }; } ``` -------------------------------- ### Parse Numbers with Manual Loop (Bad Practice) Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md This implementation is discouraged as it uses a manual loop and a mutable accumulator, which is less idiomatic and potentially less efficient than using iterator pipelines. ```Rust use core::num::ParseIntError; // Bad: manual loop + mutable accumulator pub fn parse_numbers(inputs: impl IntoIterator>) -> Result, ParseIntError> { let mut out = Vec::new(); for s in inputs { let n = s.as_ref().trim().parse::()?; out.push(n); } Ok(out) } ``` -------------------------------- ### write_to_named_temp_file Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Writes the provided buffer to a named temporary file and persists it to disk. Returns the persisted file handle and its path. This is useful for scenarios where temporary files need to be created and managed reliably. ```APIDOC ## write_to_named_temp_file ### Description Writes the provided buffer to a named temporary file and persists it to disk. Returns the persisted file handle and its path. ### Signature `pub fn write_to_named_temp_file(buf: &[u8]) -> Result<(File, PathBuf), WriteToNamedTempFileError>` ``` -------------------------------- ### Improving Debugging with `exit_result` Macro Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Demonstrates how to use the `exit_result` macro in the `main` function to display detailed error information upon program exit. This aids in understanding the root cause of errors. This snippet requires the `errgonomic` and `thiserror` crates. ```rust # #[cfg(feature = "std")] # { # use errgonomic::exit_result; # use thiserror::Error; # use std::process::ExitCode; # # #[derive(Error, Debug)] # enum Err {} # # fn run() -> Result { Ok(ExitCode::SUCCESS) } # pub fn main() -> ExitCode { exit_result(run()) } # } ``` -------------------------------- ### Macro for Parsing and Validating Input Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Parses a string input into an unsigned 32-bit integer and validates if it's an even number using the `handle!` and `handle_bool!` macros. This is useful for validating user input or configuration values. ```rust fn parse_even_number(input: &str) -> Result { use ParseEvenNumberError::*; let number = handle!(input.parse::(), InputParseFailed); handle_bool!(number % 2 != 0, NumberNotEven, number); Ok(number) } ``` -------------------------------- ### DebugAsDisplay Wrapper for Rust Types Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Wraps a type to use its Display implementation when Debug is requested. Useful for types with clear Display but complex Debug outputs. ```rust use core::fmt::{Debug, Display, Formatter}; #[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone)] pub struct DebugAsDisplay( pub T, ); impl Debug for DebugAsDisplay { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { Display::fmt(&self.0, f) } } impl Display for DebugAsDisplay { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { Display::fmt(&self.0, f) } } impl From for DebugAsDisplay { fn from(value: T) -> Self { Self(value) } } ``` -------------------------------- ### Handling External API Responses with handle! Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Uses the `handle!` macro to process the result of an external function `get_response`. It captures the prompt for potential error reporting and unwraps the successful response or maps the error to `GetAnswerError::GetResponseFailed`. ```Rust let mut response = handle!(get_response(prompt.clone()), GetResponseFailed, prompt); handle_opt_take!(response.error, ResponseContainsError, error); Ok(response.answer) ``` -------------------------------- ### Render Command to String Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Converts a `std::process::Command` into a shell-escaped string representation. It attempts to join arguments using `shlex::try_join`, falling back to just the program name if joining fails. ```rust use std::process::Command; pub fn render_command(command: &Command) -> String { let parts = core::iter::once(command.get_program().to_string_lossy()) .chain(command.get_args().map(|arg| arg.to_string_lossy())) .collect::>(); let result = shlex::try_join(parts.iter().map(|x| x.as_ref())); match result { Ok(string) => string, Err(_) => command.get_program().to_string_lossy().into_owned(), } } ``` -------------------------------- ### handle_opt! Macro for Option Error Handling Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Use `handle_opt!` to handle `Option` types, returning an error if the option is `None`. It allows passing arguments to the error variant. ```rust /// See also: [`handle_opt_take!`](crate::handle_opt_take) #[macro_export] macro_rules! handle_opt { ($option:expr, $variant:ident$(,)? $($arg:ident$(: $value:expr)?),*) => { match $option { Some(value) => value, None => return Err($variant { $($arg: $crate::_into!($arg$(: $value)?)),* }), } }; } ``` -------------------------------- ### Error Display Utility Function Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md A helper function `assert_write_eq` that takes an error and an expected string, formats the error using its `Display` implementation, and asserts equality. This is useful for testing error output. ```rust fn assert_write_eq(error: &E, expected: &str) { use std::fmt::Write; let mut actual = String::new(); let displayer = ErrorDisplayer(error); writeln!(actual, "{displayer}").unwrap(); eprintln!("{}", &actual); assert_eq!(actual, expected) } ``` -------------------------------- ### Test Case: Writing a Complex Error Structure Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Tests the `writeln_error_to_writer_and_file` function by providing a complex, nested error structure and asserting that the output matches the expected formatted string. ```rust #[cfg(test)] mod tests { use crate::functions::writeln_error::tests::JsonSchemaNewError::{InvalidInput, InvalidValues}; use crate::{ErrVec, ErrorDisplayer}; use CliRunError::*; use CommandRunError::*; use I18nRequestError::*; use I18nUpdateRunError::*; use JsonValueNewError::*; use UpdateRowError::*; use pretty_assertions::assert_eq; use std::error::Error; use thiserror::Error; #[test] fn must_write_error() { let error = CommandRunFailed { source: I18nUpdateRunFailed { source: UpdateRowsFailed { source: vec![ I18nRequestFailed { source: JsonSchemaNewFailed { source: InvalidInput { input: "foo".to_string(), }, }, row: Row::new("Foo"), }, I18nRequestFailed { source: RequestSendFailed { source: tokio::io::Error::new(tokio::io::ErrorKind::AddrNotAvailable, "server at 239.143.73.1 did not respond"), }, row: Row::new("Bar"), }, ] .into(), }, }, }; let expected = include_str!("writeln_error/fixtures/must_write_error.txt"); assert_write_eq(&error, expected); } #[test] fn must_write_nested_error() { let error = UpdateRowsFailed { source: vec![I18nRequestFailed { source: JsonSchemaNewFailed { source: InvalidValues { source: vec![ InvalidKey { key: "zed".to_string(), }, InvalidKey { key: "moo".to_string(), ``` -------------------------------- ### Use handle! instead of Result::map_err Source: https://github.com/denisgorbachev/errgonomic/blob/main/internal/guidelines.md The `handle!` macro simplifies error transformation compared to using `Result::map_err`. ```rust use errgonomic::handle; #[derive(Debug)] struct MyError; fn main() { let result: Result = Err(MyError); let value = handle!(result, err => format!("Custom error: {:?}", err)); // Note: This example is illustrative and might not compile directly without a full error type setup. } ``` -------------------------------- ### Format Error Trace Recursively Source: https://github.com/denisgorbachev/errgonomic/blob/main/DOCS.md Recursively writes a human-readable error trace to a provided formatter. Useful for displaying chained errors. ```rust use crate::{ErrorDisplayer, WriteToNamedTempFileError, map_err, write_to_named_temp_file}; use core::error::Error; use core::fmt::Formatter; use std::io; use std::io::{Write, stderr}; /// Writes a human-readable error trace to the provided formatter. pub fn writeln_error_to_formatter(error: &E, f: &mut Formatter<'_>) -> core::fmt::Result { use std::fmt::Write; write!(f, "- {error}")?; if let Some(source_new) = error.source() { f.write_char('\n')?; writeln_error_to_formatter(source_new, f) } else { Ok(()) } } ``` -------------------------------- ### Rust Option/Result Matching (Bad) Source: https://github.com/denisgorbachev/errgonomic/blob/main/AGENTS.md Avoid using `match` statements for simple transformations of `Option` or `Result` values when a method like `.map()` can achieve the same result more concisely. This pattern is verbose and less idiomatic. ```rust use core::str::FromStr; use core::num::ParseIntError; impl FromStr for UserId { type Err = ParseIntError; fn from_str(s: &str) -> Result { // This is bad because it uses more code to express the same idea match s.parse::() { Ok(value) => Ok(Self::new(value)), Err(error) => Err(error), } } } ```