Skip to content

Commit 55552b9

Browse files
authored
code refactoring changes with macro attributes added to mapped existing fields (#12)
* code refactoring changes with properties added to mapped fields * code changes for previous review * remove unnecessary import prefix * bump cargo dependencies * adding custom macro attributes for mapping fields * adding rustfmt.toml for default source code formatting config * replacing unstrigify by syn parse_sr * update dto example with custom attributes * Update README.md * Fix README.md typo * update README.md with dependencies removal * adjust README.md with more detail explanation * bump dto_mapper version from 0.2.0 to 0.3.0 Adding change log to track features and history
1 parent e00f6f6 commit 55552b9

13 files changed

+1257
-1143
lines changed

CHANGELOG.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
### Changelog
2+
All related changes will be logged here.
3+
4+
5+
## [0.3.0] 2024-11-16
6+
- Adding macro attributes features on struct, existing mapped fields and new fields
7+
8+
### Changed
9+
10+
- **BREAKING:** Remove `unstringify` library crate dependency. it is no longer required for this version. You can uninstall it
11+
by issuing this command from your project. And remove any reference of it from your code base.
12+
```shell
13+
cargo remove unstringify
14+
```
15+
16+
## [0.2.0] 2024-04-25
17+
- New computed field features added in order to create new fields from a base struct
18+
- **BREAKING:** Adding required dependency for `unstringify` library crate. It requires adding this crate to your project
19+
and import it where you need to use dto_mapper macro.
20+
21+
## [0.1.3] 2023-12-22
22+
- New computed field features added in order to create new fields from a base struct
23+
- **BREAKING:** Adding required dependency for `derive_builder` library crate. It requires adding this crate to your project
24+
and import it where you need to use dto_mapper macro.
25+
26+
## [0.1.2] 2023-11-20
27+
Initial version and publishing of dto_mapper crate.
28+
- Adding special macro mapper for dto mapping from a base struct. You can derive a struct from an existing one in order
29+
to use it for data transfer object design patterns. It has features that no existing rust dto lib crates has exposed in
30+
order to use dto design patterns in a flexible manner like "mapstruct" from Java world.
31+

Cargo.lock

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

Cargo.toml

+8-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
authors = ["James Douglass Lefruit"]
33
categories = ["data-structures", "web-programming", "rust-patterns"]
44
description = "A library to create dynamic DTOs (Data Transfer Object) from a structure"
5-
documentation = "https://github.com/douggynix/dto_mapper/tree/0.2.0"
5+
documentation = "https://github.com/douggynix/dto_mapper/tree/0.3.0"
66
edition = "2021"
77
exclude = [
88
".github/workflows/rust.yml",
@@ -14,19 +14,18 @@ license = "Apache-2.0"
1414
name = "dto_mapper"
1515
readme = "README.md"
1616
repository = "https://github.com/douggynix/dto_mapper"
17-
version = "0.2.0"
17+
version = "0.3.0"
1818
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1919
[lib]
2020
proc-macro = true
2121

2222
[dependencies]
23-
derive_builder = "0.20.0"
24-
proc-macro2 = "1.0.81"
25-
quote = "1.0.36"
26-
syn = {version = "2", features = ["full"]}
27-
unstringify = "0.1.4"
23+
derive_builder = "0.20"
24+
proc-macro2 = "1.0"
25+
quote = "1.0"
26+
syn = {version = "2.0", features = ["full"]}
2827

2928
[dev-dependencies]
30-
derive_builder = "0.20.0"
31-
serde = {version = "1", features = ["serde_derive", "derive"]}
29+
derive_builder = "0.20"
30+
serde = {version = "1.0", features = ["serde_derive", "derive"]}
3231
serde_json = {version = "1.0"}

README.md

+37-16
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ 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 **_unstringify_** and **_derive_builder_** which implements builder pattern for dto objects resulted from the dto mappers.
31+
**_dto_mapper_** library depends **_derive_builder_** which implements builder pattern for dto objects resulted from the dto mappers.
3232
By default, it generate builder for the dtos.
33+
Early versions of the dto_mapper used to depend on **_unstringify_** crate. It is no longer required anymore and has been removed.
34+
If you are using this current version. You can remove this dependency from your project.
35+
3336
You can use this instruction to install the latest version of **dto_mapper** library to your project
3437
```shell
3538
cargo add derive_builder
3639
cargo add dto_mapper
37-
cargo add unstringify
3840
```
3941
And import it to the rust source file you need to use it:
4042
```rust
@@ -44,11 +46,12 @@ use dto_mapper::DtoMapper;
4446
More details on how to use derive_builder crate here: https://crates.io/crates/derive_builder
4547

4648
# Example
47-
Let's say we want to create 3 special entities for our application
49+
Let's say we want to create special struct derived from a base existing **struct User** for our application
4850
- LoginDto that will contain only 2 fields from **User** such as _**username**_ and _**password**_. we would like to rename _**username**_ to _**login**_ in LoginDto
4951
- ProfileDto that will contain all fields from **User** and will ignore only the **password** field.
5052
- PersonDto that will contain only 3 fields from **User** such as _**firstname**_, _**lastname**_, and _**email**_. But we would like to make the _**email**_ field optional such that its final data type will be _**Option<T>**_. That is if email is **String** from User, it will result in _**Option<String>**_
51-
53+
- CustomDtoWithAttribute that will create a new field called name which will be computed from two existing fields on the struct.
54+
We will add as well serde macro attributes for serialization on the struct, on an existing and a new struct field as well
5255
It takes only those lines below to get this work done. And the conversion are being done automatically between one dto type to the original struct and vice versa.
5356

5457
```rust
@@ -62,7 +65,6 @@ It takes only those lines below to get this work done. And the conversion are be
6265
#[allow(unused)]
6366
use std::str::FromStr;
6467
#[allow(unused)]
65-
use unstringify::unstringify;
6668

6769
fn concat_str(s1: &str, s2: &str) -> String {
6870
s1.to_owned() + " " + s2
@@ -81,13 +83,19 @@ It takes only those lines below to get this work done. And the conversion are be
8183
#[mapper(
8284
dto="CustomDtoWithAttribute" ,
8385
no_builder=true ,
84-
map=[ ("email",false) ],
86+
map=[ ("email", false, ["#[serde(rename = \"email_address\")]"] ) ],
8587
derive=(Debug, Clone, Serialize, Deserialize),
86-
new_fields=[(
87-
"name: String",
88-
"concat_str( self.firstname.as_str(), self.lastname.as_str() )",
89-
["#[serde(rename = \"renamed_name\")]"], // attribute of fields
90-
)],
88+
new_fields=[
89+
(
90+
"name: String",
91+
"concat_str( self.firstname.as_str(), self.lastname.as_str() )",
92+
["#[serde(rename = \"renamed_name\")]"], // attribute of fields
93+
),
94+
(
95+
"hidden_password: String",
96+
r#""*".repeat( self.password.len() )"#
97+
),
98+
],
9199
macro_attr=["serde(rename_all = \"UPPERCASE\")"] // atriibute of struct
92100
)]
93101
struct User{
@@ -167,6 +175,15 @@ pub struct PersonDto {
167175
pub firstname: String,
168176
pub lastname: String,
169177
} // size = 72 (0x48), align = 0x8
178+
179+
#[serde(rename_all = "UPPERCASE")]
180+
pub struct CustomDtoWithAttribute {
181+
#[serde(rename = "email_address")]
182+
pub email: Option<String>,
183+
#[serde(rename = "full_name")]
184+
pub name: String,
185+
pub hidden_password: String,
186+
}
170187
```
171188

172189

@@ -203,7 +220,8 @@ impl Into<CustomDto> for User {
203220
You can install 'expand' binary crate and use ***cargo expand*** command in order to print out the DTO structures generated as shown above:
204221
```shell
205222
cargo install expand
206-
cargo expand
223+
#you can try this command from this library root directory
224+
cargo expand dto_example
207225
```
208226
# Description of macro derive and attributes for Dto Mapper
209227
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.
@@ -217,18 +235,21 @@ struct SourceStruct{ }
217235
- **Required fields** will result in build errors if not present.
218236
- **dto** : name for the dto that will result into a struct with the same name. Example : `dto="MyDto"` will result into a struct named **MyDto**.
219237
dto names must be unique. Otherwise, it will result into build errors.
220-
- **map** : an array of field names from the original struct to include or map to the new dto as fields. `map=[("fieldname:new_fieldname", required_flag )]`.
238+
- **map** : an array of field names from the original struct to include or map to the new dto as fields. `map=[("fieldname:new_fieldname", required_flag, ["field_attribute", "field_attribute"] )]`.
221239
`fieldname:new_fieldname` will rename the source field to the new one. It is not mandatory to rename. you can have `map=[("fieldname",true)]`
222240
`required_flag` can be true or false. if required_flag is false it will make the field an **Option** type in the dto.
241+
`field_attributes` are lists of macro attributes to be added to a particular field. For Example map=[("fieldname",true, ["#[serde(rename = \"full_name\")]"] )].
223242

224243
if `required_flag` is set to true, the destination dto field will be exactly of the same type with the source one in the struct.
225244
- **Optional fields**
226245
- **ignore** : an array of fieldnames not to include in the destination dtos. `ignore=["field1", "field1"]`
227246
if **ignore** is present , then **map** field becomes optional. Except if needed rename destination fields for the dto
228-
- **derive** : a list of of macro to derive from. `derive=(Debug,Clone)`
247+
- **derive** : list of of macro to derive from. `derive=(Debug,Clone)`
229248
- **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.
230-
- **new_fields** : an array of declaration of new field names to include to the resulted dto structure. `new_fields=[("fieldname:type"), ("initialize_expression") )]`.
249+
- **macro_attr**: an array of macro attributes to be added on the top of the resulted **struct**. For example : macro_attr=["serde(rename_all = \"UPPERCASE\")"]
250+
- **new_fields** : an array of declaration of new field names to include to the resulted dto structure. `new_fields=[("fieldname:type"), ("initialize_expression") ), ["macro_attribute","macro_attribute"]`.
231251
`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)]`
232252
`initialize_expression` is used an initialize value or expression to use when converting the original structure to the dto.
233-
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.
253+
`macro_attribute` will add a macro declaration on the top of this field. it is an array of attributes. It is **optional** and not required.
254+
For instance `new_fields=[( "name:String", "concat_str(self.firstname,self.lastname)" , ["#[serde(rename = \"full_name\")]"] )]` 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.
234255
**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!!**

0 commit comments

Comments
 (0)