Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

code refactoring changes with macro attributes added to mapped existing fields #12

Merged
merged 13 commits into from
Nov 16, 2024
Merged
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
### Changelog
All related changes will be logged here.


## [0.3.0] 2024-11-16
- Adding macro attributes features on struct, existing mapped fields and new fields

### Changed

- **BREAKING:** Remove `unstringify` library crate dependency. it is no longer required for this version. You can uninstall it
by issuing this command from your project. And remove any reference of it from your code base.
```shell
cargo remove unstringify
```

## [0.2.0] 2024-04-25
- New computed field features added in order to create new fields from a base struct
- **BREAKING:** Adding required dependency for `unstringify` library crate. It requires adding this crate to your project
and import it where you need to use dto_mapper macro.

## [0.1.3] 2023-12-22
- New computed field features added in order to create new fields from a base struct
- **BREAKING:** Adding required dependency for `derive_builder` library crate. It requires adding this crate to your project
and import it where you need to use dto_mapper macro.

## [0.1.2] 2023-11-20
Initial version and publishing of dto_mapper crate.
- Adding special macro mapper for dto mapping from a base struct. You can derive a struct from an existing one in order
to use it for data transfer object design patterns. It has features that no existing rust dto lib crates has exposed in
order to use dto design patterns in a flexible manner like "mapstruct" from Java world.

81 changes: 63 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 8 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
authors = ["James Douglass Lefruit"]
categories = ["data-structures", "web-programming", "rust-patterns"]
description = "A library to create dynamic DTOs (Data Transfer Object) from a structure"
documentation = "https://github.com/douggynix/dto_mapper/tree/0.2.0"
documentation = "https://github.com/douggynix/dto_mapper/tree/0.3.0"
edition = "2021"
exclude = [
".github/workflows/rust.yml",
Expand All @@ -14,19 +14,18 @@ license = "Apache-2.0"
name = "dto_mapper"
readme = "README.md"
repository = "https://github.com/douggynix/dto_mapper"
version = "0.2.0"
version = "0.3.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true

[dependencies]
derive_builder = "0.20.0"
proc-macro2 = "1.0.81"
quote = "1.0.36"
syn = {version = "2", features = ["full"]}
unstringify = "0.1.4"
derive_builder = "0.20"
proc-macro2 = "1.0"
quote = "1.0"
syn = {version = "2.0", features = ["full"]}

[dev-dependencies]
derive_builder = "0.20.0"
serde = {version = "1", features = ["serde_derive", "derive"]}
derive_builder = "0.20"
serde = {version = "1.0", features = ["serde_derive", "derive"]}
serde_json = {version = "1.0"}
53 changes: 37 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ If we have to load a 'user' record from a database, we wouldn't want to send all
This is where DTO Mapper Library comes handy.

# Installation
**_dto_mapper_** library depends on **_unstringify_** and **_derive_builder_** which implements builder pattern for dto objects resulted from the dto mappers.
**_dto_mapper_** library depends **_derive_builder_** which implements builder pattern for dto objects resulted from the dto mappers.
By default, it generate builder for the dtos.
Early versions of the dto_mapper used to depend on **_unstringify_** crate. It is no longer required anymore and has been removed.
If you are using this current version. You can remove this dependency from your project.

You can use this instruction to install the latest version of **dto_mapper** library to your project
```shell
cargo add derive_builder
cargo add dto_mapper
cargo add unstringify
```
And import it to the rust source file you need to use it:
```rust
Expand All @@ -44,11 +46,12 @@ use dto_mapper::DtoMapper;
More details on how to use derive_builder crate here: https://crates.io/crates/derive_builder

# Example
Let's say we want to create 3 special entities for our application
Let's say we want to create special struct derived from a base existing **struct User** for our application
- LoginDto that will contain only 2 fields from **User** such as _**username**_ and _**password**_. we would like to rename _**username**_ to _**login**_ in LoginDto
- ProfileDto that will contain all fields from **User** and will ignore only the **password** field.
- 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>**_

- CustomDtoWithAttribute that will create a new field called name which will be computed from two existing fields on the struct.
We will add as well serde macro attributes for serialization on the struct, on an existing and a new struct field as well
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.

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

fn concat_str(s1: &str, s2: &str) -> String {
s1.to_owned() + " " + s2
Expand All @@ -81,13 +83,19 @@ It takes only those lines below to get this work done. And the conversion are be
#[mapper(
dto="CustomDtoWithAttribute" ,
no_builder=true ,
map=[ ("email",false) ],
map=[ ("email", false, ["#[serde(rename = \"email_address\")]"] ) ],
derive=(Debug, Clone, Serialize, Deserialize),
new_fields=[(
"name: String",
"concat_str( self.firstname.as_str(), self.lastname.as_str() )",
["#[serde(rename = \"renamed_name\")]"], // attribute of fields
)],
new_fields=[
(
"name: String",
"concat_str( self.firstname.as_str(), self.lastname.as_str() )",
["#[serde(rename = \"renamed_name\")]"], // attribute of fields
),
(
"hidden_password: String",
r#""*".repeat( self.password.len() )"#
),
],
macro_attr=["serde(rename_all = \"UPPERCASE\")"] // atriibute of struct
)]
struct User{
Expand Down Expand Up @@ -167,6 +175,15 @@ pub struct PersonDto {
pub firstname: String,
pub lastname: String,
} // size = 72 (0x48), align = 0x8

#[serde(rename_all = "UPPERCASE")]
pub struct CustomDtoWithAttribute {
#[serde(rename = "email_address")]
pub email: Option<String>,
#[serde(rename = "full_name")]
pub name: String,
pub hidden_password: String,
}
```


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

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.
- **Optional fields**
- **ignore** : an array of fieldnames not to include in the destination dtos. `ignore=["field1", "field1"]`
if **ignore** is present , then **map** field becomes optional. Except if needed rename destination fields for the dto
- **derive** : a list of of macro to derive from. `derive=(Debug,Clone)`
- **derive** : list of of macro to derive from. `derive=(Debug,Clone)`
- **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.
- **new_fields** : an array of declaration of new field names to include to the resulted dto structure. `new_fields=[("fieldname:type"), ("initialize_expression") )]`.
- **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\")"]
- **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"]`.
`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)]`
`initialize_expression` is used an initialize value or expression to use when converting the original structure to the dto.
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.
`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.
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.
**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!!**
Loading
Loading