Object to Object mapper for Rust

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...

Examples

Simplest Case

``` rust use o2o::o2o;

struct Entity { someint: i32, anotherint: i16, }

[derive(o2o)]

[map(Entity)]

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.

Different field name

``` rust struct Entity { someint: i32, anotherint: i16, }

[derive(o2o)]

[map(Entity)]

struct EntityDto { someint: i32, #[map(anotherint)] different_int: i16, } ```

Different field type

``` rust struct Entity { some_int: i32, value: i16, }

[derive(o2o)]

[map(Entity)]

struct EntityDto { someint: i32, #[from(value.tostring())] //here value is a field of Entity struct #[into(value.parse::().unwrap())] //here value is a field of EntityDto struct value: String, } ```

Nested structs

``` rust struct Entity { someint: i32, child: Child, } struct Child { childint: i32, }

[derive(o2o)]

[map_owned(Entity)]

struct EntityDto { some_int: i32, #[map(child.into())] child: ChildDto }

[derive(o2o)]

[map_owned(Child)]

struct ChildDto { child_int: i32, } ```

Nested collection

``` rust struct Entity { someint: i32, children: Vec, } struct Child { childint: i32, }

[derive(o2o)]

[map(Entity)]

struct EntityDto { some_int: i32, #[map(children.iter().map(|p|p.into()).collect())] children: Vec }

[derive(o2o)]

[map(Child)]

struct ChildDto { child_int: i32, } ```

Composit example

``` rust struct Employee { id: i32, fullname: String, subordinateof: Box, subordinates: Vec> }

[derive(o2o)]

[map(Employee)]

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>>

} ```

#[map()], #[map_owned()], #[from()], #[into()] etc. explained

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

[derive(o2o)]

[from_owned(Entity)]

[from_ref(Entity)]

[owned_into(Entity)]

[ref_into(Entity)]

struct EntityDto { someint: i32, #[fromowned(anotherint)] #[fromref(anotherint)] #[ownedinto(anotherint)] #[refinto(anotherint)] differentint: i16, } ```

``` rust

[derive(o2o)]

[map(Entity)]

struct EntityDto { someint: i32, #[map(anotherint)] different_int: i16, } ```

Inline expressions and closures

So far you could have noticed a couple of different types of arguments that can be passed to member level o2o instructions:

``` rust

[map(id)] //Perhaps member name?

[from(|x| Box::new(x.subordinateof.asref().into()))] //looks like closure??

//What's this weirdness??? (I call them Inline expressions)

[map(full_name.clone())]

[map(subordinates.iter().map(|p|Box::new(p.as_ref().into())).collect())]

```

To better understand how they work, take a look at the code from previous 'composite' example, followed by the code generated by o2o:

Microservices overview

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.

Mapping uneven objects

Uneven fields

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, }

[derive(o2o)]

[map(Person)]

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 } enum ZodiacSign {} ```

In a reverse case, you need to use a struct level #[ghost()] instruction: ``` rust

[derive(o2o)]

[map(PersonDto)]

[ghost(zodiacsign: || { None })]

struct Person { id: i32, #[map(fullname.clone())] fullname: String, age: i8, }

struct PersonDto { id: i32, fullname: String, age: i8, zodiacsign: Option } enum ZodiacSign {} ```

Uneven children

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 }

[derive(o2o)]

[from(Car)]

[into_existing(Car)]

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;

[derive(o2o)]

[map(CarDto)]

struct Car { numberofdoors: i8, #[parent] vehicle: Vehicle }

[derive(o2o)]

[from(CarDto)]

[into_existing(CarDto)]

struct Vehicle { numberofseats: i16, #[parent] machine: Machine, }

[derive(o2o)]

[from(CarDto)]

[into_existing(CarDto)]

struct Machine { #[map_ref(brand.clone())] brand: String, year: i16 }

[derive(Default)]

struct CarDto { numberofdoors: i8, numberofseats: i16, brand: String, year: i16 } ```

Notice that CarDto has to implement Default trait in this case.

Tuple structs

to be documented...

Struct kind hints

to be documented...

Generics

to be documented...

Where clauses

to be documented...

Mapping to multiple structs

to be documented...

Avoiding proc macro attribute name collisions (alternative instruction syntax)

to be documented...

#[panicdebuginfo] instruction

to be documented...

Contributions

All issues, questions, pull requests are extremely welcome.

License

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.