derive_into/lib.rs
1use derive_into::try_convert_derive;
2use syn::{DeriveInput, parse_macro_input};
3
4mod attribute_parsing;
5mod derive_into;
6mod enum_convert;
7mod struct_convert;
8mod util;
9
10/** # derive-into
11
12 For more information, visit the [github repository](https://github.com/sharonex/derive-into/tree/darling-migration).
13
14 A derive macro for creating conversions between structs and enums with similar structures.
15
16 This crate provides the `#[derive(Convert)]` macro that automates implementations of
17 conversion traits (`From`, `Into`, `TryFrom`, `TryInto`) between types.
18
19 ## Basic Usage
20
21 ```rust
22 use derive_into::Convert;
23
24 #[derive(Convert)]
25 #[convert(into(path = "Destination"))]
26 struct Source {
27 id: u32,
28 #[convert(rename = "full_name")]
29 name: String,
30 }
31
32 struct Destination {
33 id: u32,
34 full_name: String,
35 }
36
37 // Usage: let destination: Destination = source.into();
38 ```
39
40 ## Attribute Reference
41
42 ### Struct/Enum Level Attributes
43
44 | Attribute | Description |
45 |-----------|-------------|
46 | `#[convert(into(path = "Type"))]` | Implements `From<Self> for Type` |
47 | `#[convert(from(path = "Type"))]` | Implements `From<Type> for Self` |
48 | `#[convert(try_into(path = "Type"))]` | Implements `TryFrom<Self> for Type` |
49 | `#[convert(try_from(path = "Type"))]` | Implements `TryFrom<Type> for Self` |
50
51 Multiple conversion attributes can be specified for a single type:
52
53 ```
54 use derive_into::Convert;
55
56 struct ApiModel {
57 version: String,
58 name: String,
59 }
60
61 struct DbModel {
62 version: String,
63 name: String,
64 }
65
66 #[derive(Convert)]
67 #[convert(into(path = "ApiModel"))]
68 #[convert(try_from(path = "DbModel"))]
69 struct DomainModel {
70 version: String,
71 name: String,
72 }
73 ```
74
75 ### Field Level Attributes
76
77 Field attributes can be applied at three different scopes:
78
79 1. **Global scope** - applies to all conversions
80 ```text
81 #[convert(rename = "new_name")]
82 ```
83
84 2. **Conversion type scope** - applies to a specific conversion type
85 ```text
86 #[convert(try_from(skip))]
87 ```
88
89 3. **Specific conversion scope** - applies to a specific conversion path
90 ```text
91 #[convert(try_from(path = "ApiModel", skip))]
92 ```
93
94 | Attribute | Description |
95 |-----------|-------------|
96 | `#[convert(rename = "new_name")]` | Maps field to different name in target |
97 | `#[convert(skip)]` | Excludes field from conversion |
98 | `#[convert(default)]` | Uses `Default::default()` for this field |
99 | `#[convert(unwrap)]` | Unwraps `Option` (`try_from` fails if `None`) |
100 | `#[convert(unwrap_or_default)]` | Automatically calls unwrap_or_default on `Option` value before converting it |
101 | `#[convert(with_func = "func_name")]` | Uses custom conversion function |
102
103 ### Custom Conversion Functions
104
105 Functions specified with `with_func` must accept a reference to the source type:
106
107 ```rust
108 use derive_into::Convert;
109
110struct ValidatedType(String);
111
112struct ApiModel {
113 field: String,
114}
115
116 #[derive(Convert)]
117 #[convert(try_from(path = "ApiModel"))]
118 struct Product {
119 #[convert(try_from(rename = "field", with_func = "validate_field"))]
120 validated: ValidatedType,
121 }
122
123 fn validate_field(source: &ApiModel) -> Result<ValidatedType, String> {
124 Ok(ValidatedType(source.field.clone()))
125 }
126 ```
127
128 ## Type Conversion Behavior
129
130 * **Direct mapping**: Identical types are copied directly
131 * **Automatic conversion**: Uses `From`/`Into` for different types
132 * **Container types**: Handles `Option<T>`, `Vec<T>`, and `HashMap<K,V>`
133 * **Nested conversions**: Converts nested structs/enums automatically
134
135 ## Container Type Examples
136
137 ### Option and Vec
138
139 ```rust
140 use derive_into::Convert;
141
142 struct Number(u8);
143 impl From<u8> for Number {
144 fn from(n: u8) -> Self {
145 Number(n)
146 }
147 }
148
149 #[derive(Convert)]
150 #[convert(into(path = "Target"))]
151 struct Source {
152 // Inner type u8 -> Number conversion happens automatically
153 optional: Option<u8>,
154 vector: Vec<u8>,
155 }
156
157 struct Target {
158 optional: Option<Number>, // Number implements From<u8>
159 vector: Vec<Number>,
160 }
161 ```
162
163 ### HashMap
164
165 ```rust
166 use derive_into::Convert;
167 use std::collections::HashMap;
168
169 #[derive(Hash, Eq, PartialEq)]
170 struct CustomString(String);
171
172 impl From<String> for CustomString {
173 fn from(s: String) -> Self {
174 CustomString(s)
175 }
176 }
177
178 struct CustomInt(u32);
179
180 impl From<u32> for CustomInt {
181 fn from(i: u32) -> Self {
182 CustomInt(i)
183 }
184 }
185
186 #[derive(Convert)]
187 #[convert(into(path = "Target"))]
188 struct Source {
189 // Both keys and values convert if they implement From/Into
190 map: HashMap<String, u32>,
191 }
192
193 struct Target {
194 map: HashMap<CustomString, CustomInt>,
195 }
196 ```
197
198 ## Enum Conversion
199
200 ```rust
201 use derive_into::Convert;
202
203 #[derive(Convert)]
204 #[convert(into(path = "TargetEnum"))]
205 enum SourceEnum {
206 Variant1(u32),
207 #[convert(rename = "RenamedVariant")]
208 Variant2 {
209 value: String,
210 #[convert(rename = "renamed_field")]
211 field: u8,
212 },
213 Unit,
214 }
215
216 enum TargetEnum {
217 Variant1(u32),
218 RenamedVariant {
219 value: String,
220 renamed_field: u8,
221 },
222 Unit,
223 }
224
225 ```
226
227 Derive macro for generating conversion implementations between similar types.
228
229 The `Convert` derive macro generates implementations of standard conversion traits
230 (`From`, `Into`, `TryFrom`, `TryInto`) between structs and enums with similar structures.
231
232 # Examples
233
234 Basic struct conversion with field renaming:
235
236 ```rust
237 use derive_into::Convert;
238
239 #[derive(Convert)]
240 #[convert(into(path = "Destination"))]
241 struct Source {
242 id: u32,
243 #[convert(rename = "full_name")]
244 name: String,
245 }
246
247 struct Destination {
248 id: u32,
249 full_name: String,
250 }
251*/
252#[proc_macro_derive(Convert, attributes(convert))]
253pub fn derive_into(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
254 let input = parse_macro_input!(input as DeriveInput);
255
256 try_convert_derive(&input)
257 .unwrap_or_else(|e| e.to_compile_error())
258 .into()
259}
260
261#[cfg(test)]
262mod tests {
263 #[test]
264 fn test_derive_macro() {
265 let t = trybuild::TestCases::new();
266 // Use the correct relative path from the project root
267 t.pass("tests/cases/basic.rs");
268 t.pass("tests/cases/test_complex_conversions.rs");
269 t.pass("tests/cases/test_enum_conversions.rs");
270 t.pass("tests/cases/test_struct_conversions.rs");
271 t.pass("tests/cases/test_field_attributes.rs");
272 }
273}