diff --git a/Cargo.toml b/Cargo.toml index b6af11e..a9d24bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ ] [workspace.package] -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/ddsha441981/struct-mapper" @@ -14,6 +14,6 @@ homepage = "https://github.com/ddsha441981/struct-mapper" documentation = "https://docs.rs/struct-mapper" rust-version = "1.71.0" authors = ["Deendayal Kumawat "] -description = "Derive macro to auto-generate From for Target by mapping struct fields" +description = "Derive macro to auto-generate From and TryFrom for Target by mapping struct fields" keywords = ["derive", "macro", "struct", "mapping", "from"] categories = ["rust-patterns", "development-tools::procedural-macro-helpers"] diff --git a/README.md b/README.md index 65b622b..fca9eda 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ # ๐Ÿ”„ struct-mapper -**Derive macro to auto-generate `From` for your structs** +**Derive macro to auto-generate `From` and `TryFrom` for your structs** **โ€” zero boilerplate field mapping.** [![CI](https://github.com/ddsha441981/struct-mapper/actions/workflows/ci.yml/badge.svg)](https://github.com/ddsha441981/struct-mapper/actions/workflows/ci.yml) -[![Crates.io](https://img.shields.io/badge/crates.io-v0.1.0-orange?style=flat-square&logo=rust)](https://crates.io/crates/struct-mapper) +[![Crates.io](https://img.shields.io/badge/crates.io-v0.2.0-orange?style=flat-square&logo=rust)](https://crates.io/crates/struct-mapper) [![Docs](https://img.shields.io/badge/docs.rs-struct--mapper-blue?style=flat-square&logo=docs.rs)](https://docs.rs/struct-mapper) [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-green?style=flat-square)](https://github.com/ddsha441981/struct-mapper) [![MSRV](https://img.shields.io/badge/MSRV-1.71.0-blue?style=flat-square&logo=rust)](https://www.rust-lang.org) @@ -17,7 +17,7 @@ --- -Stop writing tedious manual `From` implementations for struct-to-struct conversions. `struct-mapper` generates them at **compile time** with **zero runtime overhead**. +Stop writing tedious manual `From` and `TryFrom` implementations for struct-to-struct conversions. `struct-mapper` generates them at **compile time** with **zero runtime overhead**. ```rust use struct_mapper::MapFrom; @@ -103,7 +103,7 @@ Add to your `Cargo.toml`: ```toml [dependencies] -struct-mapper = "0.1" +struct-mapper = "0.2" ``` **Minimum Supported Rust Version:** `1.71.0` @@ -260,17 +260,61 @@ struct OrderResponse { --- +## ๐Ÿ”„ Fallible Conversions โ€” `TryMapFrom` (v0.2) + +When conversions can **fail** (type narrowing, parsing, validation), use `TryMapFrom`: + +```rust +use struct_mapper::TryMapFrom; +use std::num::ParseIntError; + +fn parse_port(s: String) -> Result { + s.parse::() +} + +struct RawConfig { + port_str: String, + max_conn: i64, + host: String, +} + +#[derive(TryMapFrom)] +#[try_map_from(RawConfig)] +struct ValidConfig { + #[map(from = "port_str", try_with = "parse_port")] + port: u16, // fallible: string โ†’ u16 + #[map(try_into)] + max_conn: u32, // fallible: i64 โ†’ u32 + host: String, // direct (infallible) +} + +// Success: +let raw = RawConfig { port_str: "8080".into(), max_conn: 100, host: "localhost".into() }; +let config: ValidConfig = raw.try_into().unwrap(); + +// Failure โ€” tells you exactly which field failed: +let bad = RawConfig { port_str: "not_a_port".into(), max_conn: 100, host: "x".into() }; +let err = ValidConfig::try_from(bad).unwrap_err(); +assert_eq!(err.field, "port"); +println!("{}", err); // "mapping failed at field `port`: invalid digit found in string" +``` + +--- + ## ๐Ÿ“‹ Attribute Reference | Attribute | Applies To | Description | |:----------|:----------:|:------------| | `#[map_from(Type)]` | Struct | Source type to generate `From` for | +| `#[try_map_from(Type)]` | Struct | Source type to generate `TryFrom` for | | `#[map(from = "name")]` | Field | Map from a differently-named source field | | `#[map(skip, default)]` | Field | Skip this field, use `Default::default()` | | `#[map(into)]` | Field | Call `.into()` on the source value | | `#[map(with = "fn")]` | Field | Apply a custom conversion function | +| `#[map(try_into)]` | Field | Call `.try_into()` on the source value *(TryMapFrom only)* | +| `#[map(try_with = "fn")]` | Field | Apply a fallible function *(TryMapFrom only)* | -> ๐Ÿ’ก **Tip:** Attributes can be combined: `#[map(from = "old_name", with = "convert_fn")]` +> ๐Ÿ’ก **Tip:** Attributes can be combined: `#[map(from = "old_name", try_with = "parse_fn")]` --- @@ -308,6 +352,8 @@ How does `struct-mapper` compare to alternatives? | Skip + default | โœ… | โš ๏ธ | โš ๏ธ | โš ๏ธ | | Nested `.into()` | โœ… | โœ… | โŒ | โš ๏ธ | | Custom function | โœ… | โœ… | โš ๏ธ | โš ๏ธ | +| **`TryFrom` support** | โœ… | โŒ | โŒ | โŒ | +| **Fallible custom fn** | โœ… | โŒ | โŒ | โŒ | | **Clear error messages** | โœ… | โŒ | โŒ | โŒ | | **Clean syntax** | โœ… | โš ๏ธ | โš ๏ธ | โš ๏ธ | | Compile-time only | โœ… | โœ… | โœ… | โœ… | @@ -320,15 +366,14 @@ How does `struct-mapper` compare to alternatives? - [x] `From` โ€” infallible struct conversion - [x] Field renaming, skipping, nesting, custom functions - [x] Clear compile-time error messages -- [ ] `TryFrom` โ€” fallible conversions (`v0.2`) +- [x] `TryFrom` โ€” fallible conversions (`v0.2`) โœ… - [ ] Enum variant mapping (`v0.3`) - [ ] Bi-directional mapping (`v0.4`) --- -## โš ๏ธ Limitations (v0.1) +## โš ๏ธ Limitations (v0.2) -- Only `From` (infallible conversion). `TryFrom` is planned for v0.2. - Only named struct fields. Tuple structs and enums are not yet supported. - Generics on the target struct are supported; generic source types require manual annotation. diff --git a/guide/book/404.html b/guide/book/404.html index e958a61..385fb9b 100644 --- a/guide/book/404.html +++ b/guide/book/404.html @@ -36,10 +36,10 @@ const path_to_root = ""; const default_light_theme = "ayu"; const default_dark_theme = "ayu"; - window.path_to_searchindex_js = "searchindex-37f7dcea.js"; + window.path_to_searchindex_js = "searchindex-3b3d1227.js"; - +
@@ -204,6 +204,22 @@

+ + - + @@ -204,16 +205,18 @@

#[map(skip, default)]Skip this field; use Default::default() #[map(into)]Call .into() on the source field value #[map(with = "path")]Apply a conversion function fn(SourceFieldType) -> TargetFieldType +#[map(try_into)]Call .try_into() on the source field value (TryMapFrom only) +#[map(try_with = "path")]Apply a fallible function fn(S) -> Result<T, E> (TryMapFrom only)

-

Attributes can be combined, for example: #[map(from = "old", with = "convert")]

+

Attributes can be combined, for example: #[map(from = "old", try_with = "parse")]