o2o can implement std::convert::From<T>
, std::convert::Into<T>
, and custom o2o::traits::IntoExisting<T>
traits via procedural macro. It can be best explained through examples, so...
``` rust use o2o::o2o;
struct Entity { someint: i32, anotherint: i16, }
struct EntityDto { someint: i32, anotherint: i16, } ```
For the above code o2o will generate following trait impls:
rust
impl std::convert::From<Entity> for EntityDto { ... }
impl std::convert::From<&Entity> for EntityDto { ... }
impl std::convert::Into<Entity> for EntityDto { ... }
impl std::convert::Into<Entity> for &EntityDto { ... }
With the above code you should be able to do this:
rust
let entity = Entity { some_int: 123, another_int: 321 }
let dto: EntityDto = entity.into();
and this:
rust
let dto = EntityDto { some_int: 123, another_int: 321 }
let entity: Entity = dto.into();
and a couple more things.
``` rust struct Entity { someint: i32, anotherint: i16, }
struct EntityDto { someint: i32, #[map(anotherint)] different_int: i16, } ```
``` rust struct Entity { some_int: i32, value: i16, }
struct EntityDto {
someint: i32,
#[from(value.tostring())] //here value
is a field of Entity struct
#[into(value.parse::value
is a field of EntityDto struct
value: String,
}
```
``` rust struct Entity { someint: i32, child: Child, } struct Child { childint: i32, }
struct EntityDto { some_int: i32, #[map(child.into())] child: ChildDto }
struct ChildDto { child_int: i32, } ```
``` rust
struct Entity {
someint: i32,
children: Vec
struct EntityDto {
some_int: i32,
#[map(children.iter().map(|p|p.into()).collect())]
children: Vec
struct ChildDto { child_int: i32, } ```
``` rust
struct Employee {
id: i32,
fullname: String,
subordinateof: Box
struct EmployeeDto { #[map(id)] employee_id: i32,
#[map(full_name.clone())]
full_name: String,
#[from(|x| Box::new(x.subordinate_of.as_ref().into()))]
#[into(subordinate_of, |x| Box::new(x.reports_to.as_ref().into()))]
reports_to: Box<EmployeeDto>,
#[map(subordinates.iter().map(|p|Box::new(p.as_ref().into())).collect())]
subordinates: Vec<Box<EmployeeDto>>
} ```
o2o is able to generate implementation of 6 kinds of traits:
``` rust // #[from_owned()] impl std::convert::From for B { ... }
// #[from_ref()] impl std::convert::From<&A> for B { ... }
// #[owned_into()] impl std::convert::Into for B { ... }
// #[ref_into()] impl std::convert::Into for &B { ... }
// #[ownedintoexisting()] impl o2o::traits::IntoExisting for B { ... }
// #[refintoexisting()] impl o2o::traits::IntoExisting for &B { ... } ```
And it also has shortcuts for requiring multiple implementations with just one instruction:
| | #[map()] | #[from()] | #[into()] | #[mapowned()] | #[mapref()] | #[intoexisting()] | | ---------------------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ---------------------- | | #[fromowned()] | :heavycheckmark: | :heavycheckmark: | :x: | :heavycheckmark: | :x: | :x: | | #[fromref()] | :heavycheckmark: | :heavycheckmark: | :x: | :x: | :heavycheckmark: | :x: | | #[ownedinto()] | :heavycheckmark: | :x: | :heavycheckmark: | :heavycheckmark: | :x: | :x: | | #[refinto()] | :heavycheckmark: | :x: | :heavycheckmark: | :x: | :heavycheckmark: | :x: | | #[ownedintoexisting()] | :x: | :x: | :x: | :x: | :x: | :heavycheckmark: | | #[refintoexisting()] | :x: | :x: | :x: | :x: | :x: | :heavycheck_mark: |
So two following pieces of code are equivalent:
```rust
struct EntityDto { someint: i32, #[fromowned(anotherint)] #[fromref(anotherint)] #[ownedinto(anotherint)] #[refinto(anotherint)] differentint: i16, } ```
``` rust
struct EntityDto { someint: i32, #[map(anotherint)] different_int: i16, } ```
So far you could have noticed a couple of different types of arguments that can be passed to member level o2o instructions:
``` rust
//What's this weirdness??? (I call them Inline expressions)
```
To better understand how they work, take a look at the code from previous 'composite' example, followed by the code generated by o2o:
Notice that #[map(...)]
member level o2o instructions are reflected in all four trait impls. #[from(...)]
and #[into(...)]
are only to be found in two respective implementations for From<T>
and Into<T>
traits.
o2o is able to handle scenarios when either of the structs has a field that the other struct doesn't have.
For the scenario where you put o2o instructions on a struct that contains extra field: ``` rust struct Person { id: i32, full_name: String, age: i8, }
struct PersonDto {
id: i32,
#[map(fullname.clone())]
fullname: String,
age: i8,
// (|| None) below provides default value when creating PersonDto from Person
// It could have been omited if we only needed to create Person from PersonDto
#[ghost(|| None)]
zodiac_sign: Option
In a reverse case, you need to use a struct level #[ghost()]
instruction:
``` rust
struct Person { id: i32, #[map(fullname.clone())] fullname: String, age: i8, }
struct PersonDto {
id: i32,
fullname: String,
age: i8,
zodiacsign: Option
o2o is also able to handle scenarios where a child struct (or a hierarchy of structs) on one side is flattened on the other: ``` rust struct Car { numberofdoors: i8, vehicle: Vehicle } struct Vehicle { numberofseats: i16, machine: Machine, } struct Machine { brand: String, year: i16 }
struct CarDto { numberofdoors: i8,
#[child(vehicle)]
number_of_seats: i16,
#[child(vehicle.machine)]
#[map_ref(brand.clone())]
brand: String,
#[child(vehicle.machine)]
year: i16
} ```
The reverse case, where you have to put o2o insturctions on the side that has less information, is slightly tricky: ``` rust use o2o::o2o; use o2o::traits::IntoExisting;
struct Car { numberofdoors: i8, #[parent] vehicle: Vehicle }
struct Vehicle { numberofseats: i16, #[parent] machine: Machine, }
struct Machine { #[map_ref(brand.clone())] brand: String, year: i16 }
struct CarDto { numberofdoors: i8, numberofseats: i16, brand: String, year: i16 } ```
Notice that CarDto has to implement Default
trait in this case.
to be documented...
to be documented...
to be documented...
to be documented...
to be documented...
to be documented...
to be documented...
All issues, questions, pull requests are extremely welcome.
Licensed under either an Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.