<div align="center">
# π struct-mapper
**Derive macro to auto-generate `From<Source>` and `TryFrom<Source>` for your structs**
**β zero boilerplate field mapping.**
[](https://github.com/ddsha441981/struct-mapper/actions/workflows/ci.yml)
[](https://crates.io/crates/struct-mapper)
[](https://docs.rs/struct-mapper)
[](https://github.com/ddsha441981/struct-mapper)
[](https://www.rust-lang.org)
[π Documentation](https://docs.rs/struct-mapper) Β· [π¦ Crates.io](https://crates.io/crates/struct-mapper) Β· [π Report Bug](https://github.com/ddsha441981/struct-mapper/issues) Β· [π‘ Request Feature](https://github.com/ddsha441981/struct-mapper/issues)
</div>
---
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;
struct UserEntity {
name: String,
email: String,
age: u32,
}
#[derive(MapFrom)]
#[map_from(UserEntity)]
struct UserResponse {
name: String,
email: String,
age: u32,
}
// That's it! Now you can do:
let entity = UserEntity { name: "Alice".into(), email: "a@b.com".into(), age: 30 };
let response: UserResponse = entity.into();
```
> No runtime cost. No reflection. Just a clean `impl From<>` generated at compile time.
---
## β¨ Why struct-mapper?
Every Rust backend developer writes this **dozens of times**:
<table>
<tr>
<th>π© Before β Manual boilerplate</th>
<th>π After β One derive</th>
</tr>
<tr>
<td>
```rust
impl From<UserEntity> for UserResponse {
fn from(e: UserEntity) -> Self {
UserResponse {
name: e.name,
email: e.email,
age: e.age,
display_name: e.first_name,
address: e.address.into(),
created_at: Default::default(),
}
}
}
```
</td>
<td>
```rust
#[derive(MapFrom)]
#[map_from(UserEntity)]
struct UserResponse {
name: String,
email: String,
age: u32,
#[map(from = "first_name")]
display_name: String,
#[map(into)]
address: AddressResponse,
#[map(skip, default)]
created_at: String,
}
```
</td>
</tr>
</table>
---
## π¦ Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
struct-mapper = "0.2"
```
**Minimum Supported Rust Version:** `1.71.0`
---
## π Features
### 1οΈβ£ Basic Mapping β Same Name, Same Type
Fields with matching names are mapped automatically. No attributes needed.
```rust
use struct_mapper::MapFrom;
struct Source {
name: String,
age: u32,
}
#[derive(MapFrom)]
#[map_from(Source)]
struct Target {
name: String,
age: u32,
}
let target: Target = Source { name: "Alice".into(), age: 30 }.into();
assert_eq!(target.name, "Alice");
```
### 2οΈβ£ Renamed Fields β `#[map(from = "...")]`
When source and target field names differ:
```rust
use struct_mapper::MapFrom;
struct DbRow {
user_name: String,
user_age: u32,
}
#[derive(MapFrom)]
#[map_from(DbRow)]
struct ApiUser {
#[map(from = "user_name")]
name: String,
#[map(from = "user_age")]
age: u32,
}
```
### 3οΈβ£ Skip + Default β `#[map(skip, default)]`
For fields that don't exist in the source struct:
```rust
use struct_mapper::MapFrom;
struct Entity {
name: String,
}
#[derive(MapFrom)]
#[map_from(Entity)]
struct Response {
name: String,
#[map(skip, default)]
request_id: String, // β Default::default() = ""
#[map(skip, default)]
retry_count: u32, // β Default::default() = 0
}
```
### 4οΈβ£ Nested Conversion β `#[map(into)]`
For fields where the source type implements `Into<TargetType>`:
```rust
use struct_mapper::MapFrom;
struct AddressEntity { street: String, city: String }
#[derive(MapFrom)]
#[map_from(AddressEntity)]
struct AddressDTO { street: String, city: String }
struct OrderEntity {
id: u64,
address: AddressEntity,
}
#[derive(MapFrom)]
#[map_from(OrderEntity)]
struct OrderDTO {
id: u64,
#[map(into)]
address: AddressDTO, // β source.address.into()
}
```
### 5οΈβ£ Custom Function β `#[map(with = "...")]`
For complex transformations using any function:
```rust
use struct_mapper::MapFrom;
fn cents_to_dollars(cents: u64) -> f64 {
cents as f64 / 100.0
}
struct PriceEntity {
amount_cents: u64,
}
#[derive(MapFrom)]
#[map_from(PriceEntity)]
struct PriceResponse {
#[map(from = "amount_cents", with = "cents_to_dollars")]
amount: f64,
}
```
### π Combine Everything
All attributes work together seamlessly:
```rust
use struct_mapper::MapFrom;
struct OrderEntity {
order_id: u64,
user_name: String,
total_cents: u64,
address: AddressEntity,
}
#[derive(MapFrom)]
#[map_from(OrderEntity)]
struct OrderResponse {
order_id: u64, // direct
#[map(from = "user_name")]
name: String, // renamed
#[map(from = "total_cents", with = "cents_to_dollars")]
total: f64, // renamed + custom fn
#[map(into)]
address: AddressDTO, // nested conversion
#[map(skip, default)]
request_id: String, // skipped
}
```
---
## π 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
| `#[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", try_with = "parse_fn")]`
---
## π‘οΈ Error Messages
`struct-mapper` provides **clear, actionable error messages** that tell you exactly what went wrong and how to fix it:
```
error: missing `#[map_from(SourceType)]` attribute.
Add `#[map_from(YourSourceStruct)]` to specify which struct to map from.
Example:
#[derive(MapFrom)]
#[map_from(UserEntity)]
struct UserResponse { ... }
```
```
error: `#[map(skip)]` requires `#[map(skip, default)]`.
When skipping a field, you must provide a default value.
Fix: #[map(skip, default)]
```
---
## π Comparison
How does `struct-mapper` compare to alternatives?
| Same-field mapping | β
| β
| β
| β
|
| Field renaming | β
| β
| β
| β οΈ |
| Skip + default | β
| β οΈ | β οΈ | β οΈ |
| Nested `.into()` | β
| β
| β | β οΈ |
| Custom function | β
| β
| β οΈ | β οΈ |
| **`TryFrom` support** | β
| β | β | β |
| **Fallible custom fn** | β
| β | β | β |
| **Clear error messages** | β
| β | β | β |
| **Clean syntax** | β
| β οΈ | β οΈ | β οΈ |
| Compile-time only | β
| β
| β
| β
|
| Zero runtime deps | β
| β
| β
| β
|
---
## πΊοΈ Roadmap
- [x] `From` β infallible struct conversion
- [x] Field renaming, skipping, nesting, custom functions
- [x] Clear compile-time error messages
- [x] `TryFrom` β fallible conversions (`v0.2`) β
- [ ] Enum variant mapping (`v0.3`)
- [ ] Bi-directional mapping (`v0.4`)
---
## β οΈ Limitations (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.
---
## π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
---
## π¨βπ» Author
<table>
<tr>
<td>
**Deendayal Kumawat**
[](https://www.linkedin.com/in/deendayal-kumawat/)
[](https://github.com/ddsha441981)
[](mailto:deendayal_kumawat@outlook.com)
</td>
</tr>
</table>
---
## π License
Licensed under either of:
- **Apache License, Version 2.0** β [LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>
- **MIT License** β [LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>
at your option.
---
<div align="center">
**β If you find this useful, please consider giving it a star! β**
*Made with β€οΈ in Rust*
</div>