Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ 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"
homepage = "https://github.com/ddsha441981/struct-mapper"
documentation = "https://docs.rs/struct-mapper"
rust-version = "1.71.0"
authors = ["Deendayal Kumawat <deendayal_kumawat@outlook.com>"]
description = "Derive macro to auto-generate From<Source> for Target by mapping struct fields"
description = "Derive macro to auto-generate From<Source> and TryFrom<Source> for Target by mapping struct fields"
keywords = ["derive", "macro", "struct", "mapping", "from"]
categories = ["rust-patterns", "development-tools::procedural-macro-helpers"]
61 changes: 53 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

# 🔄 struct-mapper

**Derive macro to auto-generate `From<Source>` for your structs**
**Derive macro to auto-generate `From<Source>` and `TryFrom<Source>` 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)
Expand All @@ -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;
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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<u16, ParseIntError> {
s.parse::<u16>()
}

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<Type>` for |
| `#[try_map_from(Type)]` | Struct | Source type to generate `TryFrom<Type>` 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")]`

---

Expand Down Expand Up @@ -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 | ✅ | ✅ | ✅ | ✅ |
Expand All @@ -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.

Expand Down
20 changes: 18 additions & 2 deletions guide/book/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -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";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-751d93db.js"></script>
<script src="toc-0a53999c.js"></script>
</head>
<body>
<div id="mdbook-help-container">
Expand Down Expand Up @@ -204,6 +204,22 @@ <h1 id="document-not-found-404"><a class="header" href="#document-not-found-404"
<template id=fa-play><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg></span></template>
<template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg></span></template>

<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};

window.onbeforeunload = function() {
socket.close();
}
</script>


<script>
Expand Down
31 changes: 25 additions & 6 deletions guide/book/attributes.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,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";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-751d93db.js"></script>
<script src="toc-0a53999c.js"></script>
</head>
<body>
<div id="mdbook-help-container">
Expand Down Expand Up @@ -189,7 +189,8 @@ <h2 id="struct-level-attributes"><a class="header" href="#struct-level-attribute
<tr><th style="text-align: left">Attribute</th><th style="text-align: center">Required</th><th style="text-align: left">Description</th></tr>
</thead>
<tbody>
<tr><td style="text-align: left"><code>#[map_from(Type)]</code></td><td style="text-align: center"><strong>Yes</strong></td><td style="text-align: left">Specifies the source type for <code>From&lt;Type&gt;</code> generation</td></tr>
<tr><td style="text-align: left"><code>#[map_from(Type)]</code></td><td style="text-align: center"><strong>Yes</strong> (with <code>MapFrom</code>)</td><td style="text-align: left">Specifies the source type for <code>From&lt;Type&gt;</code> generation</td></tr>
<tr><td style="text-align: left"><code>#[try_map_from(Type)]</code></td><td style="text-align: center"><strong>Yes</strong> (with <code>TryMapFrom</code>)</td><td style="text-align: left">Specifies the source type for <code>TryFrom&lt;Type&gt;</code> generation</td></tr>
</tbody>
</table>
</div>
Expand All @@ -204,16 +205,18 @@ <h2 id="field-level-attributes"><a class="header" href="#field-level-attributes"
<tr><td style="text-align: left"><code>#[map(skip, default)]</code></td><td style="text-align: left">Skip this field; use <code>Default::default()</code></td></tr>
<tr><td style="text-align: left"><code>#[map(into)]</code></td><td style="text-align: left">Call <code>.into()</code> on the source field value</td></tr>
<tr><td style="text-align: left"><code>#[map(with = "path")]</code></td><td style="text-align: left">Apply a conversion function <code>fn(SourceFieldType) -&gt; TargetFieldType</code></td></tr>
<tr><td style="text-align: left"><code>#[map(try_into)]</code></td><td style="text-align: left">Call <code>.try_into()</code> on the source field value <em>(TryMapFrom only)</em></td></tr>
<tr><td style="text-align: left"><code>#[map(try_with = "path")]</code></td><td style="text-align: left">Apply a fallible function <code>fn(S) -&gt; Result&lt;T, E&gt;</code> <em>(TryMapFrom only)</em></td></tr>
</tbody>
</table>
</div>
<p>Attributes can be combined, for example: <code>#[map(from = "old", with = "convert")]</code></p>
<p>Attributes can be combined, for example: <code>#[map(from = "old", try_with = "parse")]</code></p>

</main>

<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="features/combined.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="features/try-from.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
</a>

Expand All @@ -227,7 +230,7 @@ <h2 id="field-level-attributes"><a class="header" href="#field-level-attributes"
</div>

<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="features/combined.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="features/try-from.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
</a>

Expand All @@ -244,6 +247,22 @@ <h2 id="field-level-attributes"><a class="header" href="#field-level-attributes"
<template id=fa-play><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg></span></template>
<template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg></span></template>

<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};

window.onbeforeunload = function() {
socket.close();
}
</script>


<script>
Expand Down
20 changes: 18 additions & 2 deletions guide/book/errors.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,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";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-751d93db.js"></script>
<script src="toc-0a53999c.js"></script>
</head>
<body>
<div id="mdbook-help-container">
Expand Down Expand Up @@ -235,6 +235,22 @@ <h3 id="examples"><a class="header" href="#examples">Examples</a></h3>
<template id=fa-play><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg></span></template>
<template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg></span></template>

<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};

window.onbeforeunload = function() {
socket.close();
}
</script>


<script>
Expand Down
Loading
Loading