Skip to content

Commit

Permalink
Features/parent improvements (#23)
Browse files Browse the repository at this point in the history
* parent instr refactoring in process

* parent improvements progress

* Parent child field progress

* parent improvements in progress

* moderate refactoring

* parent improvements in progress

* Parent improvements in progress

* Small refactoring

* Parent improvements progress

* fix readme

* children instr renamed to child_parents
  • Loading branch information
Artem-Romanenia authored Nov 27, 2024
1 parent 040b46f commit d79c260
Show file tree
Hide file tree
Showing 26 changed files with 3,334 additions and 524 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "o2o"
version = "0.4.11"
edition = "2021"
authors = ["Artem Romanenia <artem.romanenia@gmail.com>"]
categories = ["rust-patterns"]
categories = ["rust-patterns", "no-std", "development-tools"]
description = "Object to Object mapper for Rust. Derive '(Try)From' and '(Try)Into' traits."
keywords = ["from", "into", "copy", "mapper", "derive"]
license = "MIT OR Apache-2.0"
Expand Down
117 changes: 76 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl From<Person> for PersonDto {
}
```

Writing code like above is not the most exciting or emotionally rewarding part of working with Rust. If you're Ok with letting procedural macro write it for you, welcome to the rest of this page.
Writing code like above is not the most exciting or rewarding part of working with Rust. If you're Ok with letting procedural macro write it for you, welcome to the rest of this page.

## Basic Example<!-- omit from toc -->

Expand Down Expand Up @@ -94,7 +94,7 @@ And here's the code that `o2o` generates (from here on, generated code is produc
}
impl ::core::convert::TryInto<Person> for PersonDto {
type Error = std::io::Error;
fn try_into(self) -> Result<Person, std::io::Error> {
fn try_into(self) -> ::core::result::Result<Person, std::io::Error> {
Ok(Person {
id: self.id,
name: self.name,
Expand All @@ -118,6 +118,7 @@ And here's the code that `o2o` generates (from here on, generated code is produc

## Some milestones<!-- omit from toc -->

* **v0.5.0** Refactoring and improved support for 'flattened' child fields: `#[child()]`, `#[child_parents()]` and `#[parent()]` instructions
* **v0.4.9** Support for `#![no_std]`
* **v0.4.4** Fallible conversions
* **v0.4.3** Enum-to-primitive type conversions with `#[literal(...)]` and `#[pattern(...)]`
Expand All @@ -144,6 +145,8 @@ And here's the code that `o2o` generates (from here on, generated code is produc
- [Item attributes (attributes for `#[] impl`, `#[] fn`, `fn() { #![] }`)](#item-attributes-attributes-for--impl--fn-fn---)
- [Slightly complex example](#slightly-complex-example)
- [Flatened children](#flatened-children)
- [Child instructions](#child-instructions)
- [Parent instructions](#parent-instructions)
- [Tuple structs](#tuple-structs)
- [Tuples](#tuples)
- [Type hints](#type-hints)
Expand Down Expand Up @@ -499,7 +502,7 @@ struct EntityDto {
}
impl ::core::convert::TryInto<Entity> for EntityDto {
type Error = std::num::ParseIntError;
fn try_into(self) -> Result<Entity, std::num::ParseIntError> {
fn try_into(self) -> ::core::result::Result<Entity, std::num::ParseIntError> {
Ok(Entity {
some_int: self.some_int,
str: self.str,
Expand All @@ -509,7 +512,7 @@ struct EntityDto {
}
impl ::core::convert::TryInto<Entity> for &EntityDto {
type Error = std::num::ParseIntError;
fn try_into(self) -> Result<Entity, std::num::ParseIntError> {
fn try_into(self) -> ::core::result::Result<Entity, std::num::ParseIntError> {
Ok(Entity {
some_int: self.some_int,
str: self.str.clone(),
Expand Down Expand Up @@ -849,7 +852,7 @@ struct Wrapper(#[from(@.parse::<i32>()?)]i32);
``` rust ignore
impl ::core::convert::TryFrom<String> for Wrapper {
type Error = std::num::ParseIntError;
fn try_from(value: String) -> Result<Wrapper, std::num::ParseIntError> {
fn try_from(value: String) -> ::core::result::Result<Wrapper, std::num::ParseIntError> {
Ok(Wrapper(value.parse::<i32>()?))
}
}
Expand Down Expand Up @@ -1129,7 +1132,9 @@ impl EmployeeDto {

### Flatened children

When the instructions are put on the side that contains flatened properties, conversion `From<T>` and `IntoExisting<T>` only requires usage of a member level `#[child(...)]` instruction, which accepts a path to the unflatened field (*without* the field name itself).
#### Child instructions

When the instructions are put on the side that contains flatened (child) properties, conversions `From<T>` and `IntoExisting<T>` only require usage of a member level `#[child(...)]` instruction, which accepts a dot separated path to the parent field (*without* the field name itself).
``` rust
use o2o::o2o;

Expand Down Expand Up @@ -1188,7 +1193,7 @@ struct CarDto {
```
</details>

When you need an `Into<T>` conversion, **o2o** also expects you to provide types for flatened properties via struct level `#[children(...)]` instruction:
When you need an `Into<T>` conversion, **o2o** also expects you to provide types for parent properties via struct level `#[child_parents(...)]` instruction:

``` rust
use o2o::o2o;
Expand All @@ -1208,7 +1213,7 @@ struct Machine {

#[derive(o2o)]
#[owned_into(Car)]
#[children(vehicle: Vehicle, vehicle.machine: Machine)]
#[child_parents(vehicle: Vehicle, vehicle.machine: Machine)]
struct CarDto {
number_of_doors: i8,

Expand Down Expand Up @@ -1243,36 +1248,29 @@ struct CarDto {
```
</details>

The reverse case, where you have to put **o2o** insturctions on the side that has field that are being flatened, is slightly tricky:
#### Parent instructions

``` rust
use o2o::o2o;
use o2o::traits::IntoExisting;
When the instructions are put on the side that contains parent property that is being flatened, conversions `Into<T>` and `IntoExisting<T>` can be done by using #[parent(...)] instruction and listing child properties:

#[derive(o2o)]
``` rust
#[derive(o2o::o2o)]
#[owned_into(CarDto)]
struct Car {
number_of_doors: i8,
#[parent]
#[parent(number_of_seats, [parent(brand, year)] machine)] // [parent] instruction can be recursive
vehicle: Vehicle
}

#[derive(o2o)]
#[owned_into_existing(CarDto)]
struct Vehicle {
number_of_seats: i16,
#[parent]
machine: Machine,
}

#[derive(o2o)]
#[owned_into_existing(CarDto)]
struct Machine {
brand: String,
year: i16
}

// CarDto has to implement `Default` trait in this case.
#[derive(Default)]
struct CarDto {
number_of_doors: i8,
Expand All @@ -1287,22 +1285,59 @@ struct CarDto {
``` rust ignore
impl ::core::convert::Into<CarDto> for Car {
fn into(self) -> CarDto {
let mut obj: CarDto = Default::default();
obj.number_of_doors = self.number_of_doors;
self.vehicle.into_existing(&mut obj);
obj
}
}
impl o2o::traits::IntoExisting<CarDto> for Vehicle {
fn into_existing(self, other: &mut CarDto) {
other.number_of_seats = self.number_of_seats;
self.machine.into_existing(other);
CarDto {
number_of_doors: self.number_of_doors,
number_of_seats: self.vehicle.number_of_seats,
brand: self.vehicle.machine.brand,
year: self.vehicle.machine.year,
}
}
}
impl o2o::traits::IntoExisting<CarDto> for Machine {
fn into_existing(self, other: &mut CarDto) {
other.brand = self.brand;
other.year = self.year;
```
</details>

When you need an `From<T>` conversion, **o2o** also expects you to provide types for nested parent properties:

``` rust
#[derive(o2o::o2o)]
#[from_owned(CarDto)]
struct Car {
number_of_doors: i8,
#[parent(number_of_seats, [parent(brand, year)] machine: Machine)] // 'machine' needs to have type here
vehicle: Vehicle
}

struct Vehicle {
number_of_seats: i16,
machine: Machine,
}

struct Machine {
brand: String,
year: i16
}

#[derive(Default)]
struct CarDto {
number_of_doors: i8,
number_of_seats: i16,
brand: String,
year: i16
}
```
<details>
<summary>View generated code</summary>

``` rust ignore
impl ::core::convert::From<CarDto> for Car {
fn from(value: CarDto) -> Car {
Car {
number_of_doors: value.number_of_doors,
vehicle: Vehicle {
number_of_seats: value.number_of_seats,
machine: Machine { brand: value.brand, year: value.year },
},
}
}
}
```
Expand Down Expand Up @@ -1799,7 +1834,7 @@ struct Machine {

#[derive(o2o)]
#[map_ref(Car)]
#[children(vehicle: Vehicle, vehicle.machine: Machine)]
#[child_parents(vehicle: Vehicle, vehicle.machine: Machine)]
#[ghosts(vehicle.machine@id: {321})]
struct CarDto {
number_of_doors: i8,
Expand Down Expand Up @@ -2049,7 +2084,7 @@ enum EnumWithDataDto {
}
impl ::core::convert::TryInto<EnumWithData> for EnumWithDataDto {
type Error = std::num::ParseIntError;
fn try_into(self) -> Result<EnumWithData, std::num::ParseIntError> {
fn try_into(self) -> ::core::result::Result<EnumWithData, std::num::ParseIntError> {
Ok(match self {
EnumWithDataDto::Item1(f0, f1) => EnumWithData::Item1(f0.parse::<i32>()?, f1),
EnumWithDataDto::Item2 { str, i_str } => EnumWithData::Item2 {
Expand Down Expand Up @@ -2241,7 +2276,7 @@ enum EnumDto {
}
impl ::core::convert::TryInto<Enum> for EnumDto {
type Error = String;
fn try_into(self) -> Result<Enum, String> {
fn try_into(self) -> ::core::result::Result<Enum, String> {
Ok(match self {
EnumDto::Var1 => Enum::Var1,
EnumDto::Var2(f0, f1) => Enum::Var2(f0, f1),
Expand Down Expand Up @@ -2277,7 +2312,7 @@ enum EnumDto {
``` rust ignore
impl ::core::convert::TryFrom<EnumDto> for Enum {
type Error = String;
fn try_from(value: EnumDto) -> Result<Enum, String> {
fn try_from(value: EnumDto) -> ::core::result::Result<Enum, String> {
Ok(match value {
EnumDto::Var1 => Enum::Var1,
EnumDto::Var2(f0, f1) => Enum::Var2(f0, f1),
Expand Down Expand Up @@ -2650,7 +2685,7 @@ enum HttpStatusFamily {
``` rust ignore
impl ::core::convert::TryFrom<i32> for HttpStatus {
type Error = StaticStr;
fn try_from(value: i32) -> Result<HttpStatus, StaticStr> {
fn try_from(value: i32) -> ::core::result::Result<HttpStatus, StaticStr> {
Ok(match value {
200 => HttpStatus::Ok,
201 => HttpStatus::Created,
Expand All @@ -2664,7 +2699,7 @@ enum HttpStatusFamily {
}
impl ::core::convert::TryInto<i32> for HttpStatus {
type Error = StaticStr;
fn try_into(self) -> Result<i32, StaticStr> {
fn try_into(self) -> ::core::result::Result<i32, StaticStr> {
Ok(match self {
HttpStatus::Ok => 200,
HttpStatus::Created => 201,
Expand All @@ -2678,7 +2713,7 @@ enum HttpStatusFamily {

impl ::core::convert::TryFrom<i32> for HttpStatusFamily {
type Error = StaticStr;
fn try_from(value: i32) -> Result<HttpStatusFamily, StaticStr> {
fn try_from(value: i32) -> ::core::result::Result<HttpStatusFamily, StaticStr> {
Ok(match value {
100..=199 => HttpStatusFamily::Information,
200..=299 => HttpStatusFamily::Success,
Expand Down
36 changes: 27 additions & 9 deletions o2o-impl/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::attr::{self};
use crate::attr::{DataTypeAttrs, MemberAttrs};
use proc_macro2::Span;
use quote::ToTokens;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{Attribute, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident, Index, Member, Result};
use syn::{Attribute, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident, Index, Member, Path, Result};

#[derive(Default)]
struct Context {
Expand Down Expand Up @@ -41,6 +42,8 @@ pub(crate) struct Field {
pub attrs: MemberAttrs,
pub idx: usize,
pub member: Member,
pub member_str: String,
pub ty: Option<Path>
}

impl<'a> Field {
Expand Down Expand Up @@ -70,16 +73,24 @@ impl<'a> Field {
.collect()
}

fn from_syn(i: usize, node: &'a syn::Field, bark: bool) -> Result<Self> {
fn from_syn(idx: usize, node: &'a syn::Field, bark: bool) -> Result<Self> {
let member = node.ident.clone().map(Member::Named).unwrap_or_else(|| {
Member::Unnamed(Index {
index: idx as u32,
span: node.ty.span(),
})
});
let member_str = member.to_token_stream().to_string();

Ok(Field {
attrs: attr::get_member_attrs(SynDataTypeMember::Field(node), bark)?,
idx: i,
member: node.ident.clone().map(Member::Named).unwrap_or_else(|| {
Member::Unnamed(Index {
index: i as u32,
span: node.ty.span(),
})
}),
idx,
member,
member_str,
ty: match &node.ty {
syn::Type::Path(p) => Some(p.path.clone()),
_ => None
}
})
}
}
Expand Down Expand Up @@ -171,6 +182,13 @@ impl<'a> DataType<'a> {
}
}

pub fn named_fields(&'a self) -> bool {
match self {
DataType::Struct(s) => s.named_fields,
DataType::Enum(_) => panic!("Method 'named_fields' is not supposed to be called in the enum context."),
}
}

pub fn get_attrs(&'a self) -> &'a DataTypeAttrs {
match self {
DataType::Struct(s) => &s.attrs,
Expand Down
Loading

0 comments on commit d79c260

Please sign in to comment.