Skip to content

Commit 610daa2

Browse files
authoredApr 25, 2024
Implement adding new field with custom expression as value (#10)
* Implement adding new field with custom expression as value * code refactoring * update readme and code refactoring * update doc version reference for crate.io
1 parent c874c83 commit 610daa2

10 files changed

+457
-173
lines changed
 

‎Cargo.lock

+31-35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[package]
22
name = "dto_mapper"
3-
version = "0.1.3"
3+
version = "0.2.0"
44
edition = "2021"
55
authors = ["James Douglass Lefruit"]
66
description = "A library to create dynamic DTOs (Data Transfer Object) from a structure"
77
repository = "https://github.com/douggynix/dto_mapper"
88
homepage = "https://github.com/douggynix/dto_mapper"
9-
documentation = "https://github.com/douggynix/dto_mapper/tree/0.1.3"
9+
documentation = "https://github.com/douggynix/dto_mapper/tree/0.2.0"
1010
readme = "README.md"
1111
license = "Apache-2.0"
1212
categories = ["data-structures", "web-programming", "rust-patterns"]
@@ -20,10 +20,12 @@ exclude = [
2020
proc-macro = true
2121

2222
[dependencies]
23-
proc-macro2 = "1.0.69"
24-
quote = "1.0.33"
25-
syn = { version = "2.0.39", features = ["full"] }
23+
derive_builder = "0.20.0"
24+
proc-macro2 = "1.0.81"
25+
quote = "1.0.36"
26+
syn = { version = "2.0.60", features = ["full"] }
27+
unstringify = "0.1.4"
2628

2729
[dev-dependencies]
28-
derive_builder = "0.12.0"
30+
derive_builder = "0.20.0"
2931

‎README.md

+57-2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ If we have to load a 'user' record from a database, we wouldn't want to send all
2828
This is where DTO Mapper Library comes handy.
2929

3030
# Installation
31-
**_dto_mapper_** library depends on **_derive_builder_** to implement builder pattern for dto objects resulted from the dto mappers.
31+
**_dto_mapper_** library depends on **_unstringify_** and **_derive_builder_** which implements builder pattern for dto objects resulted from the dto mappers.
3232
By default, it generate builder for the dtos.
3333
You can use this instruction to install the latest version of **dto_mapper** library to your project
3434
```shell
3535
cargo add derive_builder
3636
cargo add dto_mapper
37+
cargo add unstringify
3738
```
3839
And import it to the rust source file you need to use it:
3940
```rust
@@ -58,11 +59,23 @@ It takes only those lines below to get this work done. And the conversion are be
5859
***/
5960
#[macro_use]
6061
extern crate derive_builder;
62+
#[allow(unused)]
63+
use std::str::FromStr;
64+
#[allow(unused)]
65+
use unstringify::unstringify;
66+
67+
fn concat_str(s1: &str, s2: &str) -> String {
68+
s1.to_owned() + " " + s2
69+
}
6170

6271
#[derive(DtoMapper,Default,Clone)]
6372
#[mapper( dto="LoginDto" , map=[ ("username:login",true) , ("password",true)] , derive=(Debug, Clone, PartialEq) )]
6473
#[mapper( dto="ProfileDto" , ignore=["password"] , derive=(Debug, Clone, PartialEq) )]
65-
#[mapper( dto="PersonDto" , no_builder=true, map=[ ("firstname",true), ("lastname",true), ("email",false) ] )] //no_builder=true will not create default builder for that dto
74+
//no_builder=true will not create default builder for that dto
75+
#[mapper( dto="PersonDto" , no_builder=true, map=[ ("firstname",true), ("lastname",true), ("email",false) ] )]
76+
#[mapper( dto="CustomDto" , no_builder=true , map=[ ("email",false) ] , derive=(Debug, Clone) ,
77+
new_fields=[( "name: String", "concat_str( self.firstname.as_str(), self.lastname.as_str() )" )]
78+
)]
6679
struct User{
6780
username: String,
6881
password: String,
@@ -141,6 +154,43 @@ pub struct PersonDto {
141154
pub lastname: String,
142155
} // size = 72 (0x48), align = 0x8
143156
```
157+
158+
159+
Let's consider building CustomDto by adding a new field called `name` which will be initialized with concatenation of firstname and lastname fields from `User` struct with the help of **dto_mapper**:
160+
```rust
161+
let user = User {
162+
firstname: "Dessalines".into(),
163+
lastname: "Jean Jacques".into(),
164+
..User::default()
165+
};
166+
167+
println!("{:?}", user);
168+
let custom_dto: CustomDto = user.into();
169+
println!("{:?}", custom_dto);
170+
```
171+
172+
Here is how **vscode** prints the code generated by DTO mapper for **CustomDTO** and the Into Trait implemented by **User** for it
173+
```Rust
174+
pub struct CustomDto {
175+
pub email: Option<String>,
176+
pub name: String,
177+
}
178+
179+
impl Into<CustomDto> for User {
180+
fn into(self) -> CustomDto {
181+
CustomDto {
182+
email: Some(self.email),
183+
name: concat_str(self.firstname.as_str(), self.lastname.as_str()),
184+
}
185+
}
186+
}
187+
```
188+
189+
You can install 'expand' binary crate and use ***cargo expand*** command in order to print out the DTO structures generated as shown above:
190+
```shell
191+
cargo install expand
192+
cargo expand
193+
```
144194
# Description of macro derive and attributes for Dto Mapper
145195
First, DTO Mapper library requires that the source struct implements Default traits as the library relies on it to implement Into Traits for conversion between DTO and Source struct.
146196
If not it will result in error in your IDE. It is a must to to derive or implement Default for your source struct.
@@ -163,4 +213,9 @@ struct SourceStruct{ }
163213
if **ignore** is present , then **map** field becomes optional. Except if needed rename destination fields for the dto
164214
- **derive** : a list of of macro to derive from. `derive=(Debug,Clone)`
165215
- **no_builder**: a boolean flag to turn on or off builders for the dto. Default value is **_false_**. If the Dto name is "MyDto" , the builder will create a struct named "MyDtoBuilder" that can be used to build "MyDto" struct.
216+
- **new_fields** : an array of declaration of new field names to include to the resulted dto structure. `new_fields=[("fieldname:type"), ("initialize_expression") )]`.
217+
`fieldname:type` will create a new field with the `fieldname` specified and the `type`. It is not mandatory to rename. you can have `map=[("fieldname",true)]`
218+
`initialize_expression` is used an initialize value or expression to use when converting the original structure to the dto.
219+
For instance `new_fields=[("name:String"), ("concat_str(self.firstname,self.lastname)") )]` will create a new field in the dto called `name` which will be initialized with the concatenation of the original struct `firstname` and `lastname` fields. See the example above.
220+
**I would strongly suggest to use function as `initialize_expression` for more complex scenarios in case parsing is failing when writing complex inline expression directly. This will reduce code complexity!!**
166221

‎examples/dto_example.rs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use derive_builder::Builder;
2+
use dto_mapper::DtoMapper;
3+
#[allow(unused)]
4+
use std::str::FromStr;
5+
#[allow(unused)]
6+
use unstringify::unstringify;
7+
8+
fn concat_str(s1: &str, s2: &str) -> String {
9+
s1.to_owned() + " " + s2
10+
}
11+
12+
#[derive(DtoMapper, Debug, Default, Clone)]
13+
#[mapper( dto="LoginDto" , map=[ ("username:login",true) , ("password",true)] , derive=(Debug, Clone, PartialEq) )]
14+
#[mapper( dto="ProfileDto" , ignore=["password"] , derive=(Debug, Clone, PartialEq) )]
15+
#[mapper( dto="PersonDto" , no_builder=true , map=[ ("firstname",true), ("lastname",true), ("email",false) ] )]
16+
#[mapper( dto="CustomDto" , no_builder=true , map=[ ("email",false) ] , derive=(Debug, Clone) ,
17+
new_fields=[( "name: String", "concat_str( self.firstname.as_str(), self.lastname.as_str() )" )]
18+
)]
19+
struct User {
20+
username: String,
21+
password: String,
22+
email: String,
23+
firstname: String,
24+
middle_name: Option<String>,
25+
lastname: String,
26+
age: u8,
27+
}
28+
29+
fn main() {
30+
let user = User {
31+
firstname: "Dessalines".into(),
32+
lastname: "Jean Jacques".into(),
33+
..User::default()
34+
};
35+
36+
println!("{:?}", user);
37+
let custom_dto: CustomDto = user.into();
38+
println!("{:?}", custom_dto);
39+
}

0 commit comments

Comments
 (0)