Skip to content

Commit e00f6f6

Browse files
authored
PR: improving macro with adding attributes properties for struct (#11)
* fix: Fix parsing of mapper attributes with multiple values * feat: Improve code readability and efficiency - Refactored the `build_into_fields` function to simplify the logic for selecting fields. - Updated the condition in `build_into_fields` and `build_fields` to check if `map_fields` and `ignore_fields` are empty before calling `get_all_fields`. - Modified the condition in `validate_map_ignore` to use explicit return statements for better clarity. * feat: Add modifications to dto_mapper library - Added thanks to the original author in README.md - Modified the library as needed for specific use case - Updated installation instructions in README.md - Added a new mapper attribute for exact struct mapping - Updated documentation on mapper attributes in README.md * feat: Add support for parsing field types in dto_builder.rs - Import the `parse_str` function from the `syn` crate - Use the `parse_str` function to parse the `field_type` string into a `syn::Type` - Update the code to use the parsed type instead of directly using the `field_type` string - Print debug information about name and expression values during token generation * feat: Add code changes to generate DTO implementations - Remove unnecessary blank line in `generate_impl` function - Add conversion implementation for converting a struct into a DTO - Implement the `Into` trait for converting a DTO into the original struct * feat: Add expression_value and attributes to FieldEntry struct - Added `expression_value` field of type String to the `FieldEntry` struct - Added `attributes` field of type Vec<String> to the `FieldEntry` struct * feat: Add support for optional fields in DTO builder - Added `is_optional` field to the `NewField` struct - Modified the `process_new_fields` function to handle the new `is_optional` flag - Updated the `build_new_fields_token` function to include the optional flag in generated code - Updated the `MapperEntry::insert_next_field_value` function to pass the optional flag when creating a new field entry * feat: Implement changes to dto_builder.rs, mapper_entry.rs, and struct_entry.rs - Comment out the eprintln statement in dto_builder.rs - Add import statement for is_type_option function in mapper_entry.rs - Modify process_new_fields function in mapper_entry.rs to handle new field entries - Add extract_attributes helper function in mapper_entry.rs to extract attributes from an expression - Modify is_type_option function signature in struct_entry.rs to be public * fix: Remove commented out code and refactor process_new_fields function - Removed commented out code for `process_new_fields` function. - Refactored `process_new_fields` function to remove unnecessary variable declarations. - Updated the assignment of `total_passed_args` to use a non-mutable variable. * feat: Add support for required fields in DTO builder - Added `required` field to `NewField` struct in `mapper_entry.rs` - Updated `process_new_fields` function in `MapperEntry` implementation to handle the new `required` field - Modified code logic to set the value of `is_required` instead of `is_optional` - Updated error message when missing colon character for field declaration * feat: Add support for parsing macro attributes in MapperEntry - Parse the `macro_attr` field in `MapperEntry` struct - Filter out invalid macro attribute strings - Store valid macro attributes as `syn::Attribute` structs in the `macro_attr` field of `mapper_entry` - Update the code to include the parsed macro attributes in the generated DTO struct * chore: Remove commented out code in dto_builder.rs - Removed commented out code related to macro attributes in the `generate_dto_stream` function. * feat: Add modifications to dto_mapper library - Remove reference to original author's GitHub repository - Remove reference to modified version's GitHub repository * feat: Add serde and serde_json dependencies - Added `serde` and `serde_json` dependencies to the Cargo.toml file. * feat: Add new field "name" to User struct in dto_example.rs - Added a new field "name" of type String to the User struct. - The value of the "name" field is computed by concatenating the values of the "firstname" and "lastname" fields using the `concat_str` function. - Updated the mapper attributes for the User struct in dto_example.rs to include the new field. - Removed commented out code related to checking if a field is required when it has an Option type. - Refactored code in dto_builder.rs related to building initialization tokens for new fields. - Removed the `required` attribute from NewField struct in mapper_entry.rs as it was not being used. - Added support for multiple attributes on new fields in mapper_entry.rs. - Fixed a bug where an error was not thrown when the `dto` property was missing or blank in mapper_entry.rs. - Updated test_dto_creation.rs with correct syntax for specifying attributes on new fields. * fix: Refactor remove_white_space and isblank functions - Refactored the `remove_white_space` function in `utils.rs` to improve code readability. - Updated indentation for better code formatting in the `remove_white_space` function. - Renamed the parameter `str` to `string` for clarity in both functions. - Fixed inconsistent indentation in the `isblank` function. - Replaced `.as_str()` with direct string access in both functions for simplicity. - Added comments to clarify the purpose of each function.
1 parent 610daa2 commit e00f6f6

11 files changed

+1180
-930
lines changed

Cargo.lock

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

Cargo.toml

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
[package]
2-
name = "dto_mapper"
3-
version = "0.2.0"
4-
edition = "2021"
52
authors = ["James Douglass Lefruit"]
3+
categories = ["data-structures", "web-programming", "rust-patterns"]
64
description = "A library to create dynamic DTOs (Data Transfer Object) from a structure"
7-
repository = "https://github.com/douggynix/dto_mapper"
8-
homepage = "https://github.com/douggynix/dto_mapper"
95
documentation = "https://github.com/douggynix/dto_mapper/tree/0.2.0"
10-
readme = "README.md"
11-
license = "Apache-2.0"
12-
categories = ["data-structures", "web-programming", "rust-patterns"]
13-
keywords = ["dto", "dto-mapper", "data-transfer-object", "dto-pattern", "model-mapper"]
6+
edition = "2021"
147
exclude = [
15-
".github/workflows/rust.yml",
16-
".vscode/settings.json",
8+
".github/workflows/rust.yml",
9+
".vscode/settings.json",
1710
]
11+
homepage = "https://github.com/douggynix/dto_mapper"
12+
keywords = ["dto", "dto-mapper", "data-transfer-object", "dto-pattern", "model-mapper"]
13+
license = "Apache-2.0"
14+
name = "dto_mapper"
15+
readme = "README.md"
16+
repository = "https://github.com/douggynix/dto_mapper"
17+
version = "0.2.0"
1818
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1919
[lib]
2020
proc-macro = true
@@ -23,9 +23,10 @@ proc-macro = true
2323
derive_builder = "0.20.0"
2424
proc-macro2 = "1.0.81"
2525
quote = "1.0.36"
26-
syn = { version = "2.0.60", features = ["full"] }
26+
syn = {version = "2", features = ["full"]}
2727
unstringify = "0.1.4"
2828

2929
[dev-dependencies]
3030
derive_builder = "0.20.0"
31-
31+
serde = {version = "1", features = ["serde_derive", "derive"]}
32+
serde_json = {version = "1.0"}

README.md

+20-7
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
This is a library to create dynamic DTOs for rust based project. It has the same purpose with the known Java DTO Mapper Library called Model Java used mainly in Java SpringBoot applications.
33

44
DTO stands for Data Transfer Object. It is a Software Design pattern that involves mapping a parent object to a new one by reusing some of its properties into the newly created one.
5-
For example, if one application is manipulating sensitive data such as credit card information, passwords, or any other high sensitive information that shouldn't be sent(serialized) over the network or
5+
For example, if one application is manipulating sensitive data such as credit card information, passwords, or any other high sensitive information that shouldn't be sent(serialized) over the network or
66
displayed to the user in any form. So, applications need a way to map this Entity to a new one by removing the properties or fields they wouldn't want to expose to a user in JSON or any other format.
77

88
This library makes it handy. It helps annotate a structure with special attributes to extract fields to compose new structure objects for re-use. This library is based upon the **Syn** Library
99
and the power of macro derive attributes embedded in rust. The dtos are generated during compile time. There is no overhead during runtime for this as the final binary will contain the dto structure
10-
resulted after buil time. I would recommend using Visual Studio code with rust analyzer plugin installed for auto-completion and a nicer developer experience with preview of field names when hovering
10+
resulted after buil time. I would recommend using Visual Studio code with rust analyzer plugin installed for auto-completion and a nicer developer experience with preview of field names when hovering
1111
over a dto structure.
1212

1313
# Summary
@@ -29,7 +29,7 @@ This is where DTO Mapper Library comes handy.
2929

3030
# Installation
3131
**_dto_mapper_** library depends on **_unstringify_** and **_derive_builder_** which implements builder pattern for dto objects resulted from the dto mappers.
32-
By default, it generate builder for the dtos.
32+
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
@@ -71,11 +71,25 @@ It takes only those lines below to get this work done. And the conversion are be
7171
#[derive(DtoMapper,Default,Clone)]
7272
#[mapper( dto="LoginDto" , map=[ ("username:login",true) , ("password",true)] , derive=(Debug, Clone, PartialEq) )]
7373
#[mapper( dto="ProfileDto" , ignore=["password"] , derive=(Debug, Clone, PartialEq) )]
74+
// this will has 1:1 struct mapping
75+
#[mapper( dto="LoginDtoExact" , exactly=true , derive=(Debug, Clone, PartialEq) )]
7476
//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) ] )]
77+
#[mapper( dto="PersonDto" , no_builder=true, map=[ ("firstname",true), ("lastname",true), ("email",false) ] )]
7678
#[mapper( dto="CustomDto" , no_builder=true , map=[ ("email",false) ] , derive=(Debug, Clone) ,
7779
new_fields=[( "name: String", "concat_str( self.firstname.as_str(), self.lastname.as_str() )" )]
7880
)]
81+
#[mapper(
82+
dto="CustomDtoWithAttribute" ,
83+
no_builder=true ,
84+
map=[ ("email",false) ],
85+
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+
)],
91+
macro_attr=["serde(rename_all = \"UPPERCASE\")"] // atriibute of struct
92+
)]
7993
struct User{
8094
username: String,
8195
password: String,
@@ -199,7 +213,7 @@ If not it will result in error in your IDE. It is a must to to derive or impleme
199213
struct SourceStruct{ }
200214
```
201215
- ## `#[mapper()]` attributes
202-
**mapper** attributes can be repeated for as many dtos needed to be created. Each mapper represents a concrent dto struct.
216+
**mapper** attributes can be repeated for as many dtos needed to be created. Each mapper represents a concrent dto struct.
203217
- **Required fields** will result in build errors if not present.
204218
- **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**.
205219
dto names must be unique. Otherwise, it will result into build errors.
@@ -215,7 +229,6 @@ struct SourceStruct{ }
215229
- **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.
216230
- **new_fields** : an array of declaration of new field names to include to the resulted dto structure. `new_fields=[("fieldname:type"), ("initialize_expression") )]`.
217231
`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.
232+
`initialize_expression` is used an initialize value or expression to use when converting the original structure to the dto.
219233
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.
220234
**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!!**
221-

examples/dto_example.rs

+17-17
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,34 @@ use std::str::FromStr;
66
use unstringify::unstringify;
77

88
fn concat_str(s1: &str, s2: &str) -> String {
9-
s1.to_owned() + " " + s2
9+
s1.to_owned() + " " + s2
1010
}
1111

1212
#[derive(DtoMapper, Debug, Default, Clone)]
1313
#[mapper( dto="LoginDto" , map=[ ("username:login",true) , ("password",true)] , derive=(Debug, Clone, PartialEq) )]
1414
#[mapper( dto="ProfileDto" , ignore=["password"] , derive=(Debug, Clone, PartialEq) )]
1515
#[mapper( dto="PersonDto" , no_builder=true , map=[ ("firstname",true), ("lastname",true), ("email",false) ] )]
1616
#[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() )" )]
17+
new_fields=[( "name: String", "concat_str( self.firstname.as_str(), self.lastname.as_str() )" )]
1818
)]
1919
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,
20+
username: String,
21+
password: String,
22+
email: String,
23+
firstname: String,
24+
middle_name: Option<String>,
25+
lastname: String,
26+
age: u8,
2727
}
2828

2929
fn main() {
30-
let user = User {
31-
firstname: "Dessalines".into(),
32-
lastname: "Jean Jacques".into(),
33-
..User::default()
34-
};
30+
let user = User {
31+
firstname: "Dessalines".into(),
32+
lastname: "Jean Jacques".into(),
33+
..User::default()
34+
};
3535

36-
println!("{:?}", user);
37-
let custom_dto: CustomDto = user.into();
38-
println!("{:?}", custom_dto);
36+
println!("{:?}", user);
37+
let custom_dto: CustomDto = user.into();
38+
println!("{:?}", custom_dto);
3939
}

0 commit comments

Comments
 (0)