struct_mapper/lib.rs
1//! # struct-mapper
2//!
3//! **Derive macro to auto-generate `impl From<Source> for Target` by mapping struct fields.**
4//!
5//! Stop writing tedious manual `From` implementations for struct-to-struct conversions.
6//! `struct-mapper` generates them at compile time with **zero runtime overhead**.
7//!
8//! ---
9//!
10//! ## Overview
11//!
12//! In Rust backend development with frameworks like Axum, Actix, or Rocket, you constantly
13//! need to convert between different struct representations — database entities to API responses,
14//! form inputs to domain models, and so on. This leads to dozens of repetitive `impl From<A> for B`
15//! blocks that are tedious to write and maintain.
16//!
17//! `struct-mapper` eliminates this boilerplate with a single `#[derive(MapFrom)]` annotation.
18//!
19//! ## Quick Start
20//!
21//! ```rust
22//! use struct_mapper::MapFrom;
23//!
24//! // Your source struct (e.g., from database layer)
25//! struct UserEntity {
26//! name: String,
27//! email: String,
28//! age: u32,
29//! }
30//!
31//! // Target struct — From<UserEntity> is auto-generated!
32//! #[derive(Debug, MapFrom)]
33//! #[map_from(UserEntity)]
34//! struct UserResponse {
35//! name: String,
36//! email: String,
37//! age: u32,
38//! }
39//!
40//! let entity = UserEntity {
41//! name: "Khushi".to_string(),
42//! email: "khushi@gmail.com".to_string(),
43//! age: 30,
44//! };
45//!
46//! // Zero-boilerplate conversion!
47//! let response: UserResponse = entity.into();
48//! assert_eq!(response.name, "Khushi");
49//! assert_eq!(response.email, "khushi@gmail.com");
50//! assert_eq!(response.age, 30);
51//! ```
52//!
53//! ---
54//!
55//! ## Feature Guide
56//!
57//! ### 1. Basic Mapping — Same Name, Same Type
58//!
59//! When source and target fields have the **same name and same type**, no extra
60//! annotation is needed. The macro maps them automatically:
61//!
62//! ```rust
63//! use struct_mapper::MapFrom;
64//!
65//! struct Source { name: String, age: u32 }
66//!
67//! #[derive(MapFrom)]
68//! #[map_from(Source)]
69//! struct Target { name: String, age: u32 }
70//!
71//! let t: Target = Source { name: "Deendayal".into(), age: 25 }.into();
72//! assert_eq!(t.name, "Deendayal");
73//! assert_eq!(t.age, 25);
74//! ```
75//!
76//! ### 2. Field Renaming — `#[map(from = "...")]`
77//!
78//! When source and target use **different field names**, use `from` to specify
79//! which source field to read from:
80//!
81//! ```rust
82//! use struct_mapper::MapFrom;
83//!
84//! struct DbRow {
85//! user_name: String,
86//! user_age: u32,
87//! }
88//!
89//! #[derive(MapFrom)]
90//! #[map_from(DbRow)]
91//! struct ApiUser {
92//! #[map(from = "user_name")]
93//! name: String,
94//! #[map(from = "user_age")]
95//! age: u32,
96//! }
97//!
98//! let row = DbRow { user_name: "Anjali".into(), user_age: 35 };
99//! let user: ApiUser = row.into();
100//! assert_eq!(user.name, "Anjali");
101//! assert_eq!(user.age, 35);
102//! ```
103//!
104//! ### 3. Skip + Default — `#[map(skip, default)]`
105//!
106//! For target fields that **don't exist in the source**, mark them as skipped.
107//! They will be filled with [`Default::default()`]:
108//!
109//! ```rust
110//! use struct_mapper::MapFrom;
111//!
112//! struct Entity { name: String }
113//!
114//! #[derive(MapFrom)]
115//! #[map_from(Entity)]
116//! struct Response {
117//! name: String,
118//! #[map(skip, default)]
119//! request_id: String, // → "" (String::default())
120//! #[map(skip, default)]
121//! retry_count: u32, // → 0 (u32::default())
122//! }
123//!
124//! let r: Response = Entity { name: "Nikhil".into() }.into();
125//! assert_eq!(r.name, "Nikhil");
126//! assert_eq!(r.request_id, "");
127//! assert_eq!(r.retry_count, 0);
128//! ```
129//!
130//! > **Note:** `skip` always requires `default`. Using `#[map(skip)]` alone
131//! > will produce a clear compile error telling you to add `default`.
132//!
133//! ### 4. Nested Conversion — `#[map(into)]`
134//!
135//! When a source field's type implements `Into<TargetFieldType>`, use `into`
136//! to automatically call `.into()` during conversion:
137//!
138//! ```rust
139//! use struct_mapper::MapFrom;
140//!
141//! struct AddressEntity { city: String }
142//!
143//! #[derive(Debug, PartialEq, MapFrom)]
144//! #[map_from(AddressEntity)]
145//! struct AddressDTO { city: String }
146//!
147//! struct OrderEntity {
148//! id: u64,
149//! address: AddressEntity,
150//! }
151//!
152//! #[derive(Debug, MapFrom)]
153//! #[map_from(OrderEntity)]
154//! struct OrderDTO {
155//! id: u64,
156//! #[map(into)]
157//! address: AddressDTO, // source.address.into()
158//! }
159//!
160//! let order = OrderEntity {
161//! id: 42,
162//! address: AddressEntity { city: "Springfield".into() },
163//! };
164//! let dto: OrderDTO = order.into();
165//! assert_eq!(dto.id, 42);
166//! assert_eq!(dto.address.city, "Springfield");
167//! ```
168//!
169//! ### 5. Custom Function — `#[map(with = "...")]`
170//!
171//! For complex transformations, pass a **function path** that takes the source
172//! field value and returns the target field type:
173//!
174//! ```rust
175//! use struct_mapper::MapFrom;
176//!
177//! fn cents_to_dollars(cents: u64) -> f64 {
178//! cents as f64 / 100.0
179//! }
180//!
181//! struct PriceEntity { amount_cents: u64 }
182//!
183//! #[derive(MapFrom)]
184//! #[map_from(PriceEntity)]
185//! struct PriceDTO {
186//! #[map(from = "amount_cents", with = "cents_to_dollars")]
187//! amount: f64,
188//! }
189//!
190//! let dto: PriceDTO = PriceEntity { amount_cents: 1999 }.into();
191//! assert!((dto.amount - 19.99).abs() < f64::EPSILON);
192//! ```
193//!
194//! ### 6. Combining Attributes
195//!
196//! All field attributes can be **combined freely**:
197//!
198//! ```rust
199//! use struct_mapper::MapFrom;
200//!
201//! fn to_upper(s: String) -> String { s.to_uppercase() }
202//!
203//! struct Source {
204//! id: u64,
205//! user_name: String,
206//! raw_email: String,
207//! }
208//!
209//! #[derive(Debug, MapFrom)]
210//! #[map_from(Source)]
211//! struct Target {
212//! id: u64, // direct
213//! #[map(from = "user_name")]
214//! name: String, // renamed
215//! #[map(from = "raw_email", with = "to_upper")]
216//! email: String, // renamed + custom fn
217//! #[map(skip, default)]
218//! request_id: String, // skipped
219//! }
220//!
221//! let t: Target = Source {
222//! id: 1,
223//! user_name: "Sulochan".into(),
224//! raw_email: "sulochan@gmail.com".into(),
225//! }.into();
226//!
227//! assert_eq!(t.id, 1);
228//! assert_eq!(t.name, "Sulochan");
229//! assert_eq!(t.email, "SULOCHAN@GMAIL.COM");
230//! assert_eq!(t.request_id, "");
231//! ```
232//!
233//! ---
234//!
235//! ## Attribute Reference
236//!
237//! ### Struct-Level
238//!
239//! | Attribute | Required | Description |
240//! |:----------|:--------:|:------------|
241//! | `#[map_from(Type)]` | **Yes** | Specifies the source type for `From<Type>` generation |
242//!
243//! ### Field-Level
244//!
245//! | Attribute | Description |
246//! |:----------|:------------|
247//! | `#[map(from = "name")]` | Map from a differently-named source field |
248//! | `#[map(skip, default)]` | Skip this field; use `Default::default()` |
249//! | `#[map(into)]` | Call `.into()` on the source field value |
250//! | `#[map(with = "path")]` | Apply a conversion function `fn(SourceFieldType) -> TargetFieldType` |
251//!
252//! Attributes can be combined: `#[map(from = "old", with = "convert")]`
253//!
254//! ---
255//!
256//! ## Error Messages
257//!
258//! `struct-mapper` provides clear, actionable compile errors:
259//!
260//! - **Missing `#[map_from]`:** Tells you exactly which attribute to add, with an example.
261//! - **`#[map(skip)]` without `default`:** Shows the fix: `#[map(skip, default)]`.
262//! - **Contradictory attributes:** Explains why `from` + `skip` can't be combined.
263//! - **Non-struct usage:** Explains that only named-field structs are supported.
264//!
265//! ---
266//!
267//! ## How It Works
268//!
269//! The `#[derive(MapFrom)]` macro:
270//!
271//! 1. **Parses** the `#[map_from(Source)]` attribute to find the source type.
272//! 2. **Inspects** each field's `#[map(...)]` attributes.
273//! 3. **Generates** an `impl From<Source> for Target` block with the correct
274//! field assignments — direct copies, renames, `.into()` calls, or function applications.
275//!
276//! All work happens at **compile time**. The generated code is identical to what you
277//! would write by hand — zero runtime overhead, zero allocations, zero dependencies.
278//!
279//! ---
280//!
281//! ## Limitations
282//!
283//! **v0.1 constraints:**
284//!
285//! - Only infallible `From` conversion. `TryFrom` is planned for v0.2.
286//! - Only named-field structs. Tuple structs and enums are not yet supported.
287//! - Generic target structs work; generic source types need manual handling.
288
289#![deny(missing_docs)]
290
291/// Derive macro that generates `impl From<Source> for Target` by mapping struct fields.
292///
293/// Place `#[derive(MapFrom)]` on your target struct along with `#[map_from(SourceType)]`
294/// to auto-generate the `From` implementation.
295///
296/// # Example
297///
298/// ```rust
299/// use struct_mapper::MapFrom;
300///
301/// struct UserEntity {
302/// name: String,
303/// email: String,
304/// }
305///
306/// #[derive(MapFrom)]
307/// #[map_from(UserEntity)]
308/// struct UserResponse {
309/// name: String,
310/// email: String,
311/// }
312///
313/// let entity = UserEntity {
314/// name: "Khushi".to_string(),
315/// email: "khushi@gmail.com".to_string(),
316/// };
317/// let response: UserResponse = entity.into();
318/// assert_eq!(response.name, "Khushi");
319/// ```
320///
321/// # Field Attributes
322///
323/// | Attribute | Description |
324/// |:----------|:------------|
325/// | `#[map(from = "name")]` | Map from a differently-named source field |
326/// | `#[map(skip, default)]` | Skip this field, use `Default::default()` |
327/// | `#[map(into)]` | Call `.into()` on the source value |
328/// | `#[map(with = "fn_path")]` | Apply a custom conversion function |
329///
330/// Attributes can be combined: `#[map(from = "old_name", with = "convert_fn")]`
331pub use struct_mapper_derive::MapFrom;