#mapper #copy #from #into #derive

o2o

Rust 的对象到对象映射器。派生 '(Try)From' 和 '(Try)Into' 特性。

20 个版本

新版本 0.4.9 2024 年 8 月 16 日
0.4.9-alpha.1 2024 年 7 月 29 日
0.4.2 2024 年 3 月 25 日
0.3.0 2023 年 6 月 4 日

#198Rust 模式

Download history 236/week @ 2024-05-02 86/week @ 2024-05-09 137/week @ 2024-05-16 90/week @ 2024-05-23 152/week @ 2024-05-30 76/week @ 2024-06-06 70/week @ 2024-06-13 441/week @ 2024-06-20 378/week @ 2024-06-27 173/week @ 2024-07-04 76/week @ 2024-07-11 166/week @ 2024-07-18 870/week @ 2024-07-25 907/week @ 2024-08-01 955/week @ 2024-08-08 1267/week @ 2024-08-15

4,076 每月下载量

MIT/Apache

120KB
1K SLoC

Rust 的对象到对象映射器。派生 (Try)From(Try)Into 特性。

github.com crates.io docs.rs

简介

impl From<Person> for PersonDto {
    fn from(value: Person) -> PersonDto {
        PersonDto {
            id: value.id,
            name: value.name,
            age: value.age,
        }
    }
}

编写像上面的代码并不是使用 Rust 的工作中最令人兴奋或情感上最有回报的部分。如果您愿意让过程宏为您编写,那么欢迎您继续浏览本页。

基本示例

use o2o::o2o;

struct Person {
    id: u32,
    name: String,
    age: u8
}

#[derive(o2o)]
#[from_owned(Person)] // This tells o2o to generate 'From<Person> for PersonDto' implementation
#[owned_try_into(Person, std::io::Error)] // This generates 'TryInto<Person> for PersonDto' with type Error = std::io::Error
struct PersonDto {
    id: u32,
    name: String,
    age: u8
}

// Applying #[derive(o2o)] on PersonDto allows you to do this:

let person = Person { id: 123, name: "John".into(), age: 42 };
let dto = PersonDto::from(person);

assert_eq!(dto.id, 123); assert_eq!(dto.name, "John"); assert_eq!(dto.age, 42);

// and this:

let dto = PersonDto { id: 321, name: "Jack".into(), age: 23 };
let person: Person = dto.try_into().unwrap();

assert_eq!(person.id, 321); assert_eq!(person.name, "Jack"); assert_eq!(person.age, 23);

// o2o also supports enums:

enum Creature {
    Person(Person),
    Cat { nickname: String },
    Dog(String),
    Other
}

#[derive(o2o)]
#[from_owned(Creature)]
enum CreatureDto {
    Person(#[from(~.into())] PersonDto),
    Cat { nickname: String },
    Dog(String),
    Other
}

let creature = Creature::Cat { nickname: "Floppa".into() };
let dto: CreatureDto = creature.into();

if let CreatureDto::Cat { nickname } = dto { assert_eq!(nickname, "Floppa"); } else { assert!(false) }

以下是 o2o 生成的代码(从现在起,生成的代码是由 rust-analyzer: Expand macro recursively 命令产生的)

查看生成的代码
impl ::core::convert::From<Person> for PersonDto {
    fn from(value: Person) -> PersonDto {
        PersonDto {
            id: value.id,
            name: value.name,
            age: value.age,
        }
    }
}
impl ::core::convert::TryInto<Person> for PersonDto {
    type Error = std::io::Error;
    fn try_into(self) -> Result<Person, std::io::Error> {
        Ok(Person {
            id: self.id,
            name: self.name,
            age: self.age,
        })
    }
}

impl ::core::convert::From<Creature> for CreatureDto {
    fn from(value: Creature) -> CreatureDto {
        match value {
            Creature::Person(f0) => CreatureDto::Person(f0.into()),
            Creature::Cat { nickname } => CreatureDto::Cat { nickname: nickname },
            Creature::Dog(f0) => CreatureDto::Dog(f0),
            Creature::Other => CreatureDto::Other,
        }
    }
}

一些里程碑

  • v0.4.9 支持 #![no_std]
  • v0.4.4 可失败转换
  • v0.4.3 使用 #[literal(...)]#[pattern(...)] 进行枚举到原始类型转换
  • v0.4.2 基本枚举转换
  • ...

内容

特性和 o2o 特性指令

要让 o2o 知道您想要实现哪些特质,您必须使用类型级别的 o2o 特质指令(即 proc macro 属性)

struct Entity { }

#[derive(o2o::o2o)]
#[from_ref(Entity)] // This tells o2o to generate 'From<&Entity> for EntityDto' implementation
struct EntityDto { }

o2o 过程宏能够生成 12 种特质实现的实现

// When applied to a struct B:

// #[from_owned(A)]
impl ::core::convert::From<A> for B { ... }

// #[try_from_owned(A)]
impl ::core::convert::TryFrom<A> for B { ... }

// #[from_ref(A)]
impl ::core::convert::From<&A> for B { ... }

// #[try_from_ref(A)]
impl ::core::convert::TryFrom<&A> for B { ... }

// #[owned_into(A)]
impl ::core::convert::Into<A> for B { ... }

// #[try_owned_into(A)]
impl ::core::convert::TryInto<A> for B { ... }

// #[ref_into(A)]
impl ::core::convert::Into<A> for &B { ... }

// #[try_ref_into(A)]
impl ::core::convert::TryInto<A> for &B { ... }

// #[owned_into_existing(A)]
impl o2o::traits::IntoExisting<A> for B { ... }

// #[owned_try_into_existing(A)]
impl o2o::traits::TryIntoExisting<A> for B { ... }

// #[ref_into_existing(A)]
impl o2o::traits::IntoExisting<A> for &B { ... }

// #[ref_try_into_existing(A)]
impl o2o::traits::TryIntoExisting<A> for &B { ... }

o2o 还提供了简短的快捷方式,以更少的代码行配置多个特质实现

#[map()] #[from()] #[into()] #[map_owned()] #[map_ref()] #[into_existing()]
#[from_owned()] ✔️ ✔️ ✔️
#[from_ref()] ✔️ ✔️ ✔️
#[owned_into()] ✔️ ✔️ ✔️
#[ref_into()] ✔️ ✔️ ✔️
#[owned_into_existing()] ✔️
#[ref_into_existing()] ✔️

例如,以下两段代码是等效的

struct Entity { }

#[derive(o2o::o2o)]
#[map(Entity)]
struct EntityDto { }
struct Entity { }

#[derive(o2o::o2o)]
#[from_owned(Entity)]
#[from_ref(Entity)]
#[owned_into(Entity)]
#[ref_into(Entity)]
struct EntityDto { }

对于可失败转换,同样适用这些快捷方式。

安装

在一个典型的项目中,只需将此添加到 Cargo.toml

[dependencies]
o2o = "0.4.9"

no_std

#![no_std] 项目中,将此添加到 Cargo.toml

[dependencies]
o2o-macros = "0.4.9"
# Following line can be ommited if you don't need o2o to produce o2o::traits::(Try)IntoExisting implementations
o2o = { version = "0.4.9", default-features = false }

问题(不算大)

本节可能对不太熟悉 Rust 的过程宏的用户有所帮助,并解释了为什么某些事情是以这种方式完成的。

作为过程宏,o2o 只了解映射的侧面,其中应用了 #[derive(o2o)]

#[derive(o2o::o2o)]
#[map(Entity)]
struct EntityDto { }

在上面的代码中,o2o 了解关于 EntityDto 的所有内容,但它对 Entity 一无所知。它不知道它是一个结构体,不知道它有哪些字段,不知道它是一个结构体还是一个元组,甚至不知道它是否存在

因此,与 C#、Java、Go 等语言中的映射器不同,这些映射器可以使用反射来找出它们需要知道的内容,o2o 只能假设。

对于上面的代码片段,o2o 将假设

  • Entity 存在 (duh!)
  • EntityEntityDto 是相同的(在这个例子中是一个结构体)数据类型。
  • EntityEntityDto 拥有完全相同的字段。

如果 o2o 的任何假设出错,你必须告知它。

内联表达式

o2o 有一个内联表达式(Inline Expressions)的概念,可以作为一些 o2o 指令的参数传递。你可以将内联表达式视为一个闭包,它总是有两个隐含的参数:|@, ~| { ...表达式体... }|@, ~| { ...表达式体... }

  • @ 代表正在转换的对象。
  • ~ 代表正在转换的对象中特定字段的路径。
struct Entity { some_int: i32 }

#[derive(o2o::o2o)]
#[map_owned(Entity)] // tells o2o to implement 'From<Entity> for EntityDto' and 'Into<Entity> for EntityDto'
struct EntityDto { 
    #[from(~ * 2)] // Let's say for whatever reason we want to multiply 'some_int' by 2 when converting from Entity
    #[into(~ / 2)] // And divide back by 2 when converting into it
    some_int: i32
}

以下代码示例将展开为以下代码

impl ::core::convert::From<Entity> for EntityDto {
    fn from(value: Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int * 2, // '~' got replaced by 'value.some_int' for From<> implementation
        }
    }
}
impl ::core::convert::Into<Entity> for EntityDto {
    fn into(self) -> Entity {
        Entity {
            some_int: self.some_int / 2, // '~' got replaced by 'self.some_int' for Into<> implementation
        }
    }
}

要达到相同的结果,可以使用 @

struct Entity { some_int: i32 }

#[derive(o2o::o2o)]
#[map_owned(Entity)]
struct EntityDto { 
    #[from(@.some_int * 2)]
    #[into(@.some_int / 2)]
    some_int: i32
}

这会展开成完全相同的代码

impl ::core::convert::From<Entity> for EntityDto {
    fn from(value: Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int * 2, // '@' got replaced by 'value' for From<> implementation
        }
    }
}
impl ::core::convert::Into<Entity> for EntityDto {
    fn into(self) -> Entity {
        Entity {
            some_int: self.some_int / 2, // '@' got replaced by 'self' for Into<> implementation
        }
    }
}

你可以使用 ~ 为仅传递给成员级 o2o 指令的内联表达式,而 @ 可用于成员和类型级别。

所以,最后让我们看看一些示例。

结构示例

不同的成员名称

use o2o::o2o;

struct Entity {
    some_int: i32,
    another_int: i16,
}

enum EntityEnum {
    Entity(Entity),
    SomethingElse { field: i32 }
}

#[derive(o2o)]
#[map_ref(Entity)]
struct EntityDto {
    some_int: i32,
    #[map(another_int)]
    different_int: i16,
}

#[derive(o2o)]
#[map_ref(EntityEnum)]
enum EntityEnumDto {
    #[map(Entity)]
    EntityDto(#[map(~.into())]EntityDto),
    SomethingElse { 
        #[map(field, *~)]
        f: i32 
    }
}
查看生成的代码
impl ::core::convert::From<&Entity> for EntityDto {
    fn from(value: &Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int,
            different_int: value.another_int,
        }
    }
}
impl o2o::traits::IntoExisting<Entity> for &EntityDto {
    fn into_existing(self, other: &mut Entity) {
        other.some_int = self.some_int;
        other.another_int = self.different_int;
    }
}

impl ::core::convert::From<&EntityEnum> for EntityEnumDto {
    fn from(value: &EntityEnum) -> EntityEnumDto {
        match value {
            EntityEnum::Entity(f0) => EntityEnumDto::EntityDto(f0.into()),
            EntityEnum::SomethingElse { field } => EntityEnumDto::SomethingElse { f: *field },
        }
    }
}
impl ::core::convert::Into<EntityEnum> for &EntityEnumDto {
    fn into(self) -> EntityEnum {
        match self {
            EntityEnumDto::EntityDto(f0) => EntityEnum::Entity(f0.into()),
            EntityEnumDto::SomethingElse { f } => EntityEnum::SomethingElse { field: *f },
        }
    }
}

不同的字段类型

use o2o::o2o;

struct Entity {
    some_int: i32,
    str: String,
    val: i16
}

#[derive(o2o)]
#[from(Entity)]
#[try_into(Entity, std::num::ParseIntError)]
struct EntityDto {
    some_int: i32,
    #[map_ref(@.str.clone())] 
    str: String,
    #[from(~.to_string())]
    #[into(~.parse::<i16>()?)]
    val: String
}
查看生成的代码
impl ::core::convert::From<Entity> for EntityDto {
    fn from(value: Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int,
            str: value.str,
            val: value.val.to_string(),
        }
    }
}
impl ::core::convert::From<&Entity> for EntityDto {
    fn from(value: &Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int,
            str: value.str.clone(),
            val: value.val.to_string(),
        }
    }
}
impl ::core::convert::TryInto<Entity> for EntityDto {
    type Error = std::num::ParseIntError;
    fn try_into(self) -> Result<Entity, std::num::ParseIntError> {
        Ok(Entity {
            some_int: self.some_int,
            str: self.str,
            val: self.val.parse::<i16>()?,
        })
    }
}
impl ::core::convert::TryInto<Entity> for &EntityDto {
    type Error = std::num::ParseIntError;
    fn try_into(self) -> Result<Entity, std::num::ParseIntError> {
        Ok(Entity {
            some_int: self.some_int,
            str: self.str.clone(),
            val: self.val.parse::<i16>()?,
        })
    }
}

嵌套结构

use o2o::o2o;

struct Entity {
    some_int: i32,
    child: Child,
}
struct Child {
    child_int: i32,
}

#[derive(o2o)]
#[from_owned(Entity)]
struct EntityDto {
    some_int: i32,
    #[map(~.into())]
    child: ChildDto
}

#[derive(o2o)]
#[from_owned(Child)]
struct ChildDto {
    child_int: i32,
}
查看生成的代码
impl ::core::convert::From<Entity> for EntityDto {
    fn from(value: Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int,
            child: value.child.into(),
        }
    }
}

impl ::core::convert::From<Child> for ChildDto {
    fn from(value: Child) -> ChildDto {
        ChildDto {
            child_int: value.child_int,
        }
    }
}

嵌套集合

use o2o::o2o;

struct Entity {
    some_int: i32,
    children: Vec<Child>,
}
struct Child {
    child_int: i32,
}

#[derive(o2o)]
#[map_owned(Entity)]
struct EntityDto {
    some_int: i32,
    #[map(children, ~.iter().map(|p|p.into()).collect())]
    children_vec: Vec<ChildDto>
}

#[derive(o2o)]
#[map_ref(Child)]
struct ChildDto {
    child_int: i32,
}
查看生成的代码
impl ::core::convert::From<Entity> for EntityDto {
    fn from(value: Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int,
            children_vec: value.children.iter().map(|p| p.into()).collect(),
        }
    }
}
impl ::core::convert::Into<Entity> for EntityDto {
    fn into(self) -> Entity {
        Entity {
            some_int: self.some_int,
            children: self.children_vec.iter().map(|p| p.into()).collect(),
        }
    }
}
impl ::core::convert::From<&Child> for ChildDto {
    fn from(value: &Child) -> ChildDto {
        ChildDto {
            child_int: value.child_int,
        }
    }
}
impl ::core::convert::Into<Child> for &ChildDto {
    fn into(self) -> Child {
        Child {
            child_int: self.child_int,
        }
    }
}

不对称字段(跳过并提供默认值)

o2o 能够处理两种结构体中任意一种有一个字段而另一种没有的情况。

对于在包含额外字段的结构体上放置 o2o 指令的场景

use o2o::o2o;

struct Person {
    id: i32,
    full_name: String,
    age: i8,
}

#[derive(o2o)]
#[map_owned(Person)]
struct PersonDto {
    id: i32,
    full_name: 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<ZodiacSign>
}
enum ZodiacSign {}
查看生成的代码
impl ::core::convert::From<Person> for PersonDto {
    fn from(value: Person) -> PersonDto {
        PersonDto {
            id: value.id,
            full_name: value.full_name,
            age: value.age,
            zodiac_sign: None,
        }
    }
}
impl ::core::convert::Into<Person> for PersonDto {
    fn into(self) -> Person {
        Person {
            id: self.id,
            full_name: self.full_name,
            age: self.age,
        }
    }
}

在反向情况下,你需要使用结构级 #[ghosts()] 指令

use o2o::o2o;

#[derive(o2o)]
#[map_owned(PersonDto)]
#[ghosts(zodiac_sign: {None})]
struct Person {
    id: i32,
    full_name: String,
    age: i8,
}

struct PersonDto {
    id: i32,
    full_name: String,
    age: i8,
    zodiac_sign: Option<ZodiacSign>
}
enum ZodiacSign {}
查看生成的代码
impl ::core::convert::From<PersonDto> for Person {
    fn from(value: PersonDto) -> Person {
        Person {
            id: value.id,
            full_name: value.full_name,
            age: value.age,
        }
    }
}
impl ::core::convert::Into<PersonDto> for Person {
    fn into(self) -> PersonDto {
        PersonDto {
            id: self.id,
            full_name: self.full_name,
            age: self.age,
            zodiac_sign: None,
        }
    }
}

使用结构更新语法(..Default::default())

use o2o::o2o;

#[derive(Default)]
struct Entity {
    some_int: i32,
    some_float: f32
}

#[derive(Default, o2o)]
#[from(Entity| ..get_default())]
#[into(Entity| ..Default::default())]
struct EntityDto {
    some_int: i32,
    #[ghost]
    some_string: String
}

fn get_default() -> EntityDto {
    EntityDto { some_int: 0, some_string: "test".into() }
}
查看生成的代码
impl ::core::convert::From<&Entity> for EntityDto {
    fn from(value: &Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int,
            ..get_default()
        }
    }
}
impl ::core::convert::Into<Entity> for EntityDto {
    fn into(self) -> Entity {
        Entity {
            some_int: self.some_int,
            ..Default::default()
        }
    }
}

定义辅助变量

use o2o::o2o;

struct Person {
    age: i8,
    first_name: String,
    last_name: String
}

#[derive(o2o)]
#[from_owned(Person| vars(first_name: {@.first_name}, last_name: {@.last_name}))]
#[owned_into(Person| vars(first: {"John"}, last: {"Doe"}))]
#[ghosts(first_name: {first.into()}, last_name: {last.into()})]
struct PersonDto {
    age: i8,
    #[ghost({format!("{} {}", first_name, last_name)})]
    full_name: String
}
查看生成的代码
impl ::core::convert::From<Person> for PersonDto {
    fn from(value: Person) -> PersonDto {
        let first_name = value.first_name;
        let last_name = value.last_name;
        PersonDto {
            age: value.age,
            full_name: format!("{} {}", first_name, last_name),
        }
    }
}
impl ::core::convert::Into<Person> for PersonDto {
    fn into(self) -> Person {
        let first = "John";
        let last = "Doe";
        Person {
            age: self.age,
            first_name: first.into(),
            last_name: last.into(),
        }
    }
}

快速返回

o2o 允许你通过指定紧跟在 return 之后的快速返回内联表达式来绕过大部分逻辑

use o2o::o2o;

#[derive(o2o)]
#[owned_into(String| return @.0.to_string())]
#[try_from_owned(String, std::num::ParseIntError)]
struct Wrapper(#[from(@.parse::<i32>()?)]i32);
查看生成的代码
impl ::core::convert::TryFrom<String> for Wrapper {
    type Error = std::num::ParseIntError;
    fn try_from(value: String) -> Result<Wrapper, std::num::ParseIntError> {
        Ok(Wrapper(value.parse::<i32>()?))
    }
}
impl ::core::convert::Into<String> for Wrapper {
    fn into(self) -> String {
        self.0.to_string()
    }
}

快速返回与辅助变量配合得很好

use o2o::o2o;

#[derive(o2o)]
#[owned_into(i32| vars(hrs: {@.hours as i32}, mns: {@.minutes as i32}, scs: {@.seconds as i32}), 
    return hrs * 3600 + mns * 60 + scs)]
struct Time {
    hours: i8,
    minutes: i8,
    seconds: i8,
}
查看生成的代码
impl ::core::convert::Into<i32> for Time {
    fn into(self) -> i32 {
        let hrs = self.hours as i32;
        let mns = self.minutes as i32;
        let scs = self.seconds as i32;
        hrs * 3600 + mns * 60 + scs
    }
}

重复特性指令参数

#[derive(o2o::o2o)]
// Defining original 'template' instruction
#[from_owned(std::num::ParseIntError| repeat(), return Self(@.to_string()))]
#[from_owned(std::num::ParseFloatError)]
#[from_owned(std::num::TryFromIntError)]
#[from_owned(std::str::ParseBoolError)]
struct MyError(String);
查看生成的代码
impl ::core::convert::From<std::num::ParseIntError> for MyError {
    fn from(value: std::num::ParseIntError) -> MyError {
        Self(value.to_string())
    }
}
impl ::core::convert::From<std::num::ParseFloatError> for MyError {
    fn from(value: std::num::ParseFloatError) -> MyError {
        Self(value.to_string())
    }
}
impl ::core::convert::From<std::num::TryFromIntError> for MyError {
    fn from(value: std::num::TryFromIntError) -> MyError {
        Self(value.to_string())
    }
}
impl ::core::convert::From<std::str::ParseBoolError> for MyError {
    fn from(value: std::str::ParseBoolError) -> MyError {
        Self(value.to_string())
    }
}

重复的指令可能被跳过或结束

#[derive(o2o::o2o)]

// Defining original 'template' instruction
#[from_owned(std::num::ParseIntError| repeat(), return Self(@.to_string()))]

// Original instruction is repeated for this conversion
#[from_owned(std::num::ParseFloatError)]

// Do not use (skip) original instruction
#[from_owned(std::num::TryFromIntError| skip_repeat, return Self("Custom TryFromIntError message".into()))]

// Original instruction is repeated for this conversion
#[from_owned(std::str::ParseBoolError)]

// Original instruction is repeated for this conversion
#[from_owned(std::char::ParseCharError)]

// Stop repeating original instruction, define and start repeating a new one
#[from_owned(std::net::AddrParseError| stop_repeat, repeat(), return Self("other".into()))]

// New instruction is repeated for this conversion
#[from_owned(std::io::Error)] 
struct MyError(String);
查看生成的代码
impl ::core::convert::From<std::num::ParseIntError> for MyError {
    fn from(value: std::num::ParseIntError) -> MyError {
        Self(value.to_string())
    }
}
impl ::core::convert::From<std::num::ParseFloatError> for MyError {
    fn from(value: std::num::ParseFloatError) -> MyError {
        Self(value.to_string())
    }
}
impl ::core::convert::From<std::num::TryFromIntError> for MyError {
    fn from(value: std::num::TryFromIntError) -> MyError {
        Self("Custom TryFromIntError message".into())
    }
}
impl ::core::convert::From<std::str::ParseBoolError> for MyError {
    fn from(value: std::str::ParseBoolError) -> MyError {
        Self(value.to_string())
    }
}
impl ::core::convert::From<std::char::ParseCharError> for MyError {
    fn from(value: std::char::ParseCharError) -> MyError {
        Self(value.to_string())
    }
}
impl ::core::convert::From<std::net::AddrParseError> for MyError {
    fn from(value: std::net::AddrParseError) -> MyError {
        Self("other".into())
    }
}
impl ::core::convert::From<std::io::Error> for MyError {
    fn from(value: std::io::Error) -> MyError {
        Self("other".into())
    }
}

项目属性(用于 #[] impl#[] fnfn() { #![] })的属性

struct TestDto {
    x: i32
}

#[derive(o2o::o2o)]
#[from_owned(TestDto| 
    impl_attribute(cfg(any(foo, bar))),
    attribute(inline(always)), 
    inner_attribute(allow(unused_variables))
)]
struct Test {
    x: i32,
}
查看生成的代码
#[cfg(any(foo, bar))]
impl ::core::convert::From<TestDto> for Test {
    #[inline(always)]
    fn from(value: TestDto) -> Test {
        #![allow(unused_variables)]
        Test { x: value.x }
    }
}

稍微复杂一点的例子

use o2o::o2o;

struct Employee {
    id: i32,
    first_name: String,
    last_name: String,
    subordinate_of: Box<Employee>,
    subordinates: Vec<Box<Employee>>
}
impl Employee {
    fn get_full_name(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
    }
}

#[derive(o2o)]
#[map(Employee)]
#[ghosts(
    first_name: {@.get_first_name()},
    last_name: {@.get_last_name()}
)]
struct EmployeeDto {
    #[map(id)]
    employee_id: i32,
    #[ghost(@.get_full_name())]
    full_name: String,

    #[from(Box::new(@.subordinate_of.as_ref().into()))]
    #[into(subordinate_of, Box::new(@.reports_to.as_ref().into()))]
    reports_to: Box<EmployeeDto>,

    #[map(~.iter().map(|p| Box::new(p.as_ref().into())).collect())]
    subordinates: Vec<Box<EmployeeDto>>
}
impl EmployeeDto {
    fn get_first_name(&self) -> String {
        self.full_name.split_whitespace().collect::<Vec<&str>>()[0].into()
    }
    fn get_last_name(&self) -> String {
        self.full_name.split_whitespace().collect::<Vec<&str>>()[1].into()
    }
}
查看生成的代码
impl ::core::convert::From<Employee> for EmployeeDto {
    fn from(value: Employee) -> EmployeeDto {
        EmployeeDto {
            employee_id: value.id,
            full_name: value.get_full_name(),
            reports_to: (|x: &Employee| Box::new(x.subordinate_of.as_ref().into()))(&value),
            subordinates: value.subordinates.iter().map(|p| Box::new(p.as_ref().into())).collect(),
        }
    }
}
impl ::core::convert::From<&Employee> for EmployeeDto {
    fn from(value: &Employee) -> EmployeeDto {
        EmployeeDto {
            employee_id: value.id,
            full_name: value.get_full_name(),
            reports_to: (|x: &Employee| Box::new(x.subordinate_of.as_ref().into()))(value),
            subordinates: value.subordinates.iter().map(|p| Box::new(p.as_ref().into())).collect(),
        }
    }
}
impl ::core::convert::Into<Employee> for EmployeeDto {
    fn into(self) -> Employee {
        Employee {
            id: self.employee_id,
            subordinate_of: (|x: &EmployeeDto| Box::new(x.reports_to.as_ref().into()))(&self),
            subordinates: self.subordinates.iter().map(|p| Box::new(p.as_ref().into())).collect(),
            first_name: (|x: &EmployeeDto| x.get_first_name())(&self),
            last_name: self.get_last_name(),
        }
    }
}
impl ::core::convert::Into<Employee> for &EmployeeDto {
    fn into(self) -> Employee {
        Employee {
            id: self.employee_id,
            subordinate_of: (|x: &EmployeeDto| Box::new(x.reports_to.as_ref().into()))(self),
            subordinates: self.subordinates.iter().map(|p| Box::new(p.as_ref().into())).collect(),
            first_name: (|x: &EmployeeDto| x.get_first_name())(self),
            last_name: self.get_last_name(),
        }
    }
}

扁平化的子元素

当指令放置在包含扁平化属性的侧边时,转换 From<T>IntoExisting<T> 仅需要使用成员级 #[child(...)] 指令,该指令接受一个指向未扁平化字段的路径(不包含字段名称本身)。

use o2o::o2o;

struct Car {
    number_of_doors: i8,
    vehicle: Vehicle
}
struct Vehicle {
    number_of_seats: i16,
    machine: Machine,
}
struct Machine {
    brand: String,
    year: i16
}

#[derive(o2o)]
#[from_owned(Car)]
#[ref_into_existing(Car)]
struct CarDto {
    number_of_doors: i8,

    #[child(vehicle)]
    number_of_seats: i16,

    #[child(vehicle.machine)]
    #[map_ref(~.clone())]
    brand: String,

    #[child(vehicle.machine)]
    year: i16
}
查看生成的代码
impl ::core::convert::From<Car> for CarDto {
    fn from(value: Car) -> CarDto {
        CarDto {
            number_of_doors: value.number_of_doors,
            number_of_seats: value.vehicle.number_of_seats,
            brand: value.vehicle.machine.brand,
            year: value.vehicle.machine.year,
        }
    }
}
impl o2o::traits::IntoExisting<Car> for &CarDto {
    fn into_existing(self, other: &mut Car) {
        other.number_of_doors = self.number_of_doors;
        other.vehicle.number_of_seats = self.number_of_seats;
        other.vehicle.machine.brand = self.brand.clone();
        other.vehicle.machine.year = self.year;
    }
}

当需要 Into<T> 转换时,o2o 也要求你通过结构级 #[children(...)] 指令提供扁平化属性的类型

use o2o::o2o;

struct Car {
    number_of_doors: i8,
    vehicle: Vehicle
}
struct Vehicle {
    number_of_seats: i16,
    machine: Machine,
}
struct Machine {
    brand: String,
    year: i16
}

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

    #[child(vehicle)]
    number_of_seats: i16,

    #[child(vehicle.machine)]
    brand: String,

    #[child(vehicle.machine)]
    year: i16
}
查看生成的代码
impl ::core::convert::Into<Car> for CarDto {
    fn into(self) -> Car {
        Car {
            number_of_doors: self.number_of_doors,
            vehicle: Vehicle {
                number_of_seats: self.number_of_seats,
                machine: Machine {
                    brand: self.brand,
                    year: self.year,
                },
            },
        }
    }
}

反向情况,即你需要在包含要扁平化字段的侧边放置 o2o 指令,稍微有些棘手

use o2o::o2o;
use o2o::traits::IntoExisting;

#[derive(o2o)]
#[owned_into(CarDto)]
struct Car {
    number_of_doors: i8,
    #[parent]
    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,
    number_of_seats: i16,
    brand: String,
    year: i16
}
查看生成的代码
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);
    }
}
impl o2o::traits::IntoExisting<CarDto> for Machine {
    fn into_existing(self, other: &mut CarDto) {
        other.brand = self.brand;
        other.year = self.year;
    }
}

元组结构体

use o2o::o2o;

struct TupleEntity(i32, String);

#[derive(o2o)]
#[map_ref(TupleEntity)]
struct TupleEntityDto(i32, #[map_ref(~.clone())] String);
查看生成的代码
impl ::core::convert::From<&TupleEntity> for TupleEntityDto {
    fn from(value: &TupleEntity) -> TupleEntityDto {
        TupleEntityDto(value.0, value.1.clone())
    }
}
impl ::core::convert::Into<TupleEntity> for &TupleEntityDto {
    fn into(self) -> TupleEntity {
        TupleEntity(self.0, self.1.clone())
    }
}

只要 Rust 允许以下语法,将 o2o 指令放置在命名侧边,就可以轻松地在元组结构体和命名结构体之间进行转换

use o2o::o2o;

struct TupleEntity(i32, String);

#[derive(o2o)]
#[map_ref(TupleEntity)]
struct EntityDto {
    #[map_ref(0)]
    some_int: i32, 
    #[map_ref(1, ~.clone())]
    some_str: String
}
查看生成的代码
impl ::core::convert::From<&TupleEntity> for EntityDto {
    fn from(value: &TupleEntity) -> EntityDto {
        EntityDto {
            some_int: value.0,
            some_str: value.1.clone(),
        }
    }
}
impl ::core::convert::Into<TupleEntity> for &EntityDto {
    fn into(self) -> TupleEntity {
        TupleEntity {
            0: self.some_int,
            1: self.some_str.clone(),
        }
    }
}

元组

use o2o::o2o;

#[derive(o2o)]
#[map_ref((i32, String))]
pub struct Entity{
    #[map(0)]
    int: i32,
    #[map(1, ~.clone())]
    string: String,
}
查看生成的代码
impl ::core::convert::From<&(i32, String)> for Entity {
    fn from(value: &(i32, String)) -> Entity {
        Entity {
            int: value.0,
            string: value.1.clone(),
        }
    }
}
impl ::core::convert::Into<(i32, String)> for &Entity {
    fn into(self) -> (i32, String) {
        (self.int, self.string.clone())
    }
}

类型提示

默认情况下,o2o 将假设另一侧的结构与原始结构类型相同。当您需要在元组一侧放置指令时,为了在命名和元组结构之间进行转换,您需要使用类型提示。

use o2o::o2o;

#[derive(o2o)]
#[map_owned(EntityDto as {})]
struct TupleEntity(#[map(some_int)] i32, #[map(some_str)] String);

struct EntityDto{
    some_int: i32, 
    some_str: String
}
查看生成的代码
impl ::core::convert::From<EntityDto> for TupleEntity {
    fn from(value: EntityDto) -> TupleEntity {
        TupleEntity(value.some_int, value.some_str)
    }
}
impl ::core::convert::Into<EntityDto> for TupleEntity {
    fn into(self) -> EntityDto {
        EntityDto {
            some_int: self.0,
            some_str: self.1,
        }
    }
}

泛型

use o2o::o2o;

struct Entity<T> {
    some_int: i32,
    something: T,
}

#[derive(o2o)]
#[map_owned(Entity::<f32>)]
struct EntityDto {
    some_int: i32,
    something: f32
}
查看生成的代码
impl ::core::convert::From<Entity<f32>> for EntityDto {
    fn from(value: Entity<f32>) -> EntityDto {
        EntityDto {
            some_int: value.some_int,
            something: value.something,
        }
    }
}
impl ::core::convert::Into<Entity<f32>> for EntityDto {
    fn into(self) -> Entity<f32> {
        Entity::<f32> {
            some_int: self.some_int,
            something: self.something,
        }
    }
}

Where子句

use o2o::o2o;

struct Child<T> {
    child_int: i32,
    something: T,
}

#[derive(o2o)]
#[map_owned(Child::<T>)]
#[where_clause(T: Clone)]
struct ChildDto<T> {
    child_int: i32,
    #[map(something, ~.clone())]
    stuff: T,
}
查看生成的代码
impl<T> ::core::convert::From<Child<T>> for ChildDto<T> where T: Clone, {
    fn from(value: Child<T>) -> ChildDto<T> {
        ChildDto {
            child_int: value.child_int,
            stuff: value.something.clone(),
        }
    }
}
impl<T> ::core::convert::Into<Child<T>> for ChildDto<T> where T: Clone, {
    fn into(self) -> Child<T> {
        Child::<T> {
            child_int: self.child_int,
            something: self.stuff.clone(),
        }
    }
}

映射到多个结构体

use o2o::o2o;

struct Person {
    full_name: String,
    age: i32,
    country: String,
}

struct PersonModel {
    full_name: String,
    age: i32,
    place_of_birth: String,
}

#[derive(o2o)]
#[ref_into(Person)]
#[ref_into(PersonModel)]
struct PersonDto {
    // 'Default' member level instruction applies to all types
    #[into(full_name, ~.clone())]
    name: String,
    age: i32,
    // 'Dedicated' member level instruction applies to a specific type only
    #[into(Person| country, ~.clone())]
    #[into(PersonModel| ~.clone())]
    place_of_birth: String,
}
查看生成的代码
impl ::core::convert::Into<Person> for &PersonDto {
    fn into(self) -> Person {
        Person {
            full_name: self.name.clone(),
            age: self.age,
            country: self.place_of_birth.clone(),
        }
    }
}
impl ::core::convert::Into<PersonModel> for &PersonDto {
    fn into(self) -> PersonModel {
        PersonModel {
            full_name: self.name.clone(),
            age: self.age,
            place_of_birth: self.place_of_birth.clone(),
        }
    }
}

避免 proc macro 属性名冲突(替代指令语法)

o2o proc宏声明了许多属性,其中一些具有相当广泛的意义(例如from、into、map、child、parent等),因此如果您需要与其他proc宏一起使用,这些属性可能会发生冲突,并且不清楚它们应该应用哪个proc宏。针对这种情况,o2o 支持两种替代语法(syntacies?)

以下,所有三种 o2o proc宏的应用都将产生相同的生成代码

use o2o::o2o;

struct Entity {
    some_int: i32,
    val: i16,
    str: String
}
// =====================================================================
#[derive(o2o)]
#[from(Entity)]
#[try_into(Entity, std::num::ParseIntError)]
struct EntityDto1 {
    some_int: i32,
    #[from(~.to_string())]
    #[into(~.parse::<i16>()?)]
    val: String,
    #[map_ref(~.clone())] 
    str: String
}
// =====================================================================
#[derive(o2o)]
#[o2o(from(Entity))]
#[o2o(try_into(Entity, std::num::ParseIntError))]
struct EntityDto2 {
    some_int: i32,
    #[o2o(from(~.to_string()))]
    #[o2o(into(~.parse::<i16>()?))]
    val: String,
    #[o2o(map_ref(~.clone()))] 
    str: String
}
// =====================================================================
#[derive(o2o)]
#[o2o(
    from(Entity),
    try_into(Entity, std::num::ParseIntError)
)]
struct EntityDto3 {
    some_int: i32,
    #[o2o(
        from(~.to_string()),
        try_into(~.parse::<i16>()?),
    )]
    val: String,
    #[o2o(map_ref(~.clone()))] 
    str: String
}
// =====================================================================

此语法适用于所有受支持的struct和成员级别指令。

通过 #[o2o(...)] 语法获取额外 o2o 指令

原始类型转换

use o2o::o2o;

struct Entity {
    some_int: i32,
    some_float: f32
}

#[derive(o2o)]
#[o2o(map_ref(Entity))]
struct EntityDto {
    #[o2o(as_type(i32))]
    some_int: i16,
    #[o2o(as_type(some_float, f32))]
    another_int: i16
}
查看生成的代码
impl ::core::convert::From<&Entity> for EntityDto {
    fn from(value: &Entity) -> EntityDto {
        EntityDto {
            some_int: value.some_int as i16,
            another_int: value.some_float as i16,
        }
    }
}
impl ::core::convert::Into<Entity> for &EntityDto {
    fn into(self) -> Entity {
        Entity {
            some_int: self.some_int as i32,
            some_float: self.another_int as f32,
        }
    }
}

这将对支持 'as' 转换的所有类型都有效。

重复成员指令

use o2o::o2o;

struct Car {
    number_of_doors: i8,
    vehicle: Vehicle
}
struct Vehicle {
    number_of_seats: i16,
    can_fly: bool,
    needs_driver: bool,
    horsepower: i32,
    top_speed: f32,
    machine: Machine,
}
struct Machine {
    id: i32,
    brand: String,
    year: i16,
    weight: f32,
    length: f32,
    width: f32,
    height: f32,
}

#[derive(o2o)]
#[map_ref(Car)]
#[children(vehicle: Vehicle, vehicle.machine: Machine)]
#[ghosts(vehicle.machine@id: {321})]
struct CarDto {
    number_of_doors: i8,

    // #[o2o(repeat)] will repeat all instructions for this member to the following members, 
    // until there is a #[o2o(stop_repeat)] or the members run out.
    #[o2o(repeat)] #[child(vehicle)]
    number_of_seats: i16,
    can_fly: bool,
    needs_driver: bool,
    horsepower: i32,
    top_speed: f32,
    #[o2o(stop_repeat)]

    // You can also specify what specific types of instructions to repeat
    // (supported values are 'map', 'child', 'parent', 'ghost')
    #[o2o(repeat(child))] #[child(vehicle.machine)]
    #[map(~.clone())]
    brand: String,
    year: i16,
    weight: f32,
    length: f32,
    width: f32,
    height: f32,
    #[o2o(stop_repeat)]

    #[o2o(repeat)] #[ghost({123})]
    useless_param: i32,
    useless_param_2: i32,
    useless_param_3: i32,
}
查看生成的代码
impl ::core::convert::From<&Car> for CarDto {
    fn from(value: &Car) -> CarDto {
        CarDto {
            number_of_doors: value.number_of_doors,
            number_of_seats: value.vehicle.number_of_seats,
            can_fly: value.vehicle.can_fly,
            needs_driver: value.vehicle.needs_driver,
            horsepower: value.vehicle.horsepower,
            top_speed: value.vehicle.top_speed,
            brand: value.vehicle.machine.brand.clone(),
            year: value.vehicle.machine.year,
            weight: value.vehicle.machine.weight,
            length: value.vehicle.machine.length,
            width: value.vehicle.machine.width,
            height: value.vehicle.machine.height,
            useless_param: 123,
            useless_param_2: 123,
            useless_param_3: 123,
        }
    }
}
impl ::core::convert::Into<Car> for &CarDto {
    fn into(self) -> Car {
        Car {
            number_of_doors: self.number_of_doors,
            vehicle: Vehicle {
                number_of_seats: self.number_of_seats,
                can_fly: self.can_fly,
                needs_driver: self.needs_driver,
                horsepower: self.horsepower,
                top_speed: self.top_speed,
                machine: Machine {
                    brand: self.brand.clone(),
                    year: self.year,
                    weight: self.weight,
                    length: self.length,
                    width: self.width,
                    height: self.height,
                    id: 321,
                },
            },
        }
    }
}

对于枚举变体字段进行“渗透”重复

如果您想在以下变体的字段上重复执行,可以在重复指令中使用 permeate()

enum Enum {
    Var1 { field: i32, field_2: i32 },
    Var2 { field_3: i32 },
    Var3 { field_4: i32 },
    Var4 { field_5: i32 },
    Var5 { str: &'static str },
}

#[derive(o2o::o2o)]
#[map_owned(Enum)]
enum EnumDto {
    Var1 {
        #[o2o(repeat(permeate()))] 
        #[from(~ * 2)] 
        #[into(~ / 2)] 

        field: i32,
        field_2: i32
     },
    Var2 { field_3: i32 },
    Var3 { field_4: i32 },
    Var4 { field_5: i32 },
    Var5 { #[o2o(stop_repeat)] str: &'static str },
}
查看生成的代码
impl ::core::convert::From<Enum> for EnumDto {
    fn from(value: Enum) -> EnumDto {
        match value {
            Enum::Var1 { field, field_2 } => EnumDto::Var1 {
                field: field * 2,
                field_2: field_2 * 2,
            },
            Enum::Var2 { field_3 } => EnumDto::Var2 {
                field_3: field_3 * 2,
            },
            Enum::Var3 { field_4 } => EnumDto::Var3 {
                field_4: field_4 * 2,
            },
            Enum::Var4 { field_5 } => EnumDto::Var4 {
                field_5: field_5 * 2,
            },
            Enum::Var5 { str } => EnumDto::Var5 { str: str },
        }
    }
}
impl ::core::convert::Into<Enum> for EnumDto {
    fn into(self) -> Enum {
        match self {
            EnumDto::Var1 { field, field_2 } => Enum::Var1 {
                field: field / 2,
                field_2: field_2 / 2,
            },
            EnumDto::Var2 { field_3 } => Enum::Var2 {
                field_3: field_3 / 2,
            },
            EnumDto::Var3 { field_4 } => Enum::Var3 {
                field_4: field_4 / 2,
            },
            EnumDto::Var4 { field_5 } => Enum::Var4 {
                field_5: field_5 / 2,
            },
            EnumDto::Var5 { str } => Enum::Var5 { str: str },
        }
    }
}

枚举示例

不同的变体名称

pub enum Sort {
    ASC,
    DESC,
    None
}

#[derive(o2o::o2o)]
#[map_owned(Sort)]
pub enum SortDto {
    #[map(ASC)] Ascending,
    #[map(DESC)] Descending,
    None
}
查看生成的代码
impl ::core::convert::From<Sort> for SortDto {
    fn from(value: Sort) -> SortDto {
        match value {
            Sort::ASC => SortDto::Ascending,
            Sort::DESC => SortDto::Descending,
            Sort::None => SortDto::None,
        }
    }
}
impl ::core::convert::Into<Sort> for SortDto {
    fn into(self) -> Sort {
        match self {
            SortDto::Ascending => Sort::ASC,
            SortDto::Descending => Sort::DESC,
            SortDto::None => Sort::None,
        }
    }
}

不同的枚举变体字段名称和类型

enum EnumWithData {
    Item1(i32, i16),
    Item2 { str: String, i: i32 },
}

#[derive(o2o::o2o)]
#[from_owned(EnumWithData)]
#[owned_try_into(EnumWithData, std::num::ParseIntError)]
enum EnumWithDataDto {
    Item1(
        #[from(~.to_string())] 
        #[into(~.parse::<i32>()?)]
        String, 
        i16
    ),
    Item2 {
        str: String,
        #[from(i, ~.to_string())] 
        #[into(i, ~.parse::<i32>()?)]
        i_str: String 
    },
}
查看生成的代码
impl ::core::convert::From<EnumWithData> for EnumWithDataDto {
    fn from(value: EnumWithData) -> EnumWithDataDto {
        match value {
            EnumWithData::Item1(f0, f1) => EnumWithDataDto::Item1(f0.to_string(), f1),
            EnumWithData::Item2 { str, i } => EnumWithDataDto::Item2 {
                str: str,
                i_str: i.to_string(),
            },
        }
    }
}
impl ::core::convert::TryInto<EnumWithData> for EnumWithDataDto {
    type Error = std::num::ParseIntError;
    fn try_into(self) -> 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 {
                str: str,
                i: i_str.parse::<i32>()?,
            },
        })
    }
}

或者可以这样操作

enum EnumWithData {
    Item1(i32, i16),
    Item2 { str: String, i: i32 },
}

#[derive(o2o::o2o)]
#[from_owned(EnumWithData)]
#[owned_try_into(EnumWithData, std::num::ParseIntError)]
enum EnumWithDataDto {
    // When applied to enum variants, ~ replaces 'RightSideEnum::VariantName'
    #[from(~(f0.to_string(), f1))] // ~ is 'EnumWithDataDto::Item1'
    #[into(~(f0.parse::<i32>()?, f1))] // ~ is 'EnumWithData::Item1'
    Item1(String, i16),
    #[from(~{ str, i_str: i.to_string()})] 
    #[into(~{ str, i: i_str.parse::<i32>()? })]
    Item2 { str: String, #[map(i)]i_str: String },
}

此示例将产生与上述示例完全相同的代码。

枚举变体类型提示

映射到单元枚举变体

#[derive(o2o::o2o)]
#[owned_into(EnumDto)]
enum Enum {
    Var1,
    #[type_hint(as Unit)]
    Var2(i32, String),
    #[type_hint(as Unit)]
    Var3 {_field: i32, _str_field: String}
}

enum EnumDto {
    Var1,
    Var2,
    Var3
}
查看生成的代码
impl ::core::convert::Into<EnumDto> for Enum {
    fn into(self) -> EnumDto {
        match self {
            Enum::Var1 => EnumDto::Var1,
            Enum::Var2(f0, f1) => EnumDto::Var2,
            Enum::Var3 { _field, _str_field } => EnumDto::Var3,
        }
    }
}

反向示例

enum Enum {
    Var1,
    Var2(i32, String),
    Var3 { _field: i32, _str_field: String }
}

#[derive(o2o::o2o)]
#[from(Enum)]
enum EnumDto {
    Var1,
    #[type_hint(as ())] Var2,
    #[type_hint(as {})] Var3
}
查看生成的代码
impl ::core::convert::From<Enum> for EnumDto {
    fn from(value: Enum) -> EnumDto {
        match value {
            Enum::Var1 => EnumDto::Var1,
            Enum::Var2(..) => EnumDto::Var2,
            Enum::Var3 { .. } => EnumDto::Var3,
        }
    }
}

在结构体和元组变体之间进行映射

#[derive(o2o::o2o)]
#[map_owned(EnumDto)]
enum Enum {
    Var1,

    #[type_hint(as ())]
    Var2 { field: i32 },

    #[type_hint(as {})]
    Var3(
        #[map_owned(str_field)]
        String
    )
}

enum EnumDto {
    Var1,
    Var2(i32),
    Var3 {str_field: String}
}
查看生成的代码
impl ::core::convert::From<EnumDto> for Enum {
    fn from(value: EnumDto) -> Enum {
        match value {
            EnumDto::Var1 => Enum::Var1,
            EnumDto::Var2(f0) => Enum::Var2 { field: f0 },
            EnumDto::Var3 { str_field } => Enum::Var3(str_field),
        }
    }
}
impl ::core::convert::Into<EnumDto> for Enum {
    fn into(self) -> EnumDto {
        match self {
            Enum::Var1 => EnumDto::Var1,
            Enum::Var2 { field } => EnumDto::Var2(field),
            Enum::Var3(f0) => EnumDto::Var3 { str_field: f0 },
        }
    }
}

枚举幽灵变体

enum Enum {
    Var1,
    Var2(i32, String),
}

#[derive(o2o::o2o)]
#[from_owned(Enum)]
#[owned_try_into(Enum, String)]
enum EnumDto {
    Var1,
    Var2(i32, String),
    #[ghost({Err(format!("unknown: {}", _str_field))?})]
    Var3 { _field: i32, _str_field: String }
}
查看生成的代码
impl ::core::convert::From<Enum> for EnumDto {
    fn from(value: Enum) -> EnumDto {
        match value {
            Enum::Var1 => EnumDto::Var1,
            Enum::Var2(f0, f1) => EnumDto::Var2(f0, f1),
        }
    }
}
impl ::core::convert::TryInto<Enum> for EnumDto {
    type Error = String;
    fn try_into(self) -> Result<Enum, String> {
        Ok(match self {
            EnumDto::Var1 => Enum::Var1,
            EnumDto::Var2(f0, f1) => Enum::Var2(f0, f1),
            EnumDto::Var3 { _field, _str_field } => Err(format!("unknown: {}", _str_field))?,
        })
    }
}

反向案例

#[derive(o2o::o2o)]
#[try_from_owned(EnumDto, String)]
#[owned_into(EnumDto)]
#[ghosts(Var3 { _str_field, .. }: {Err(format!("Unknown: {}", _str_field))?})]
enum Enum {
    Var1,
    Var2(i32, String),
}

enum EnumDto {
    Var1,
    Var2(i32, String),
    Var3 { _field: i32, _str_field: String }
}
查看生成的代码
impl ::core::convert::TryFrom<EnumDto> for Enum {
    type Error = String;
    fn try_from(value: EnumDto) -> Result<Enum, String> {
        Ok(match value {
            EnumDto::Var1 => Enum::Var1,
            EnumDto::Var2(f0, f1) => Enum::Var2(f0, f1),
            EnumDto::Var3 { _str_field, .. } => Err(format!("Unknown: {}", _str_field))?,
        })
    }
}
impl ::core::convert::Into<EnumDto> for Enum {
    fn into(self) -> EnumDto {
        match self {
            Enum::Var1 => EnumDto::Var1,
            Enum::Var2(f0, f1) => EnumDto::Var2(f0, f1),
        }
    }
}

枚举变体幽灵字段

跳过字段并提供默认值

#[derive(o2o::o2o)]
#[map_owned(EnumDto)]
enum Enum {
    Var1,
    Var2 {
        field: i32,
        #[ghost(321.0)]
        _f: f32,
    },
    Var3(
        i32,
        #[ghost({123.0})]
        f32,
    )
}

enum EnumDto {
    Var1,
    Var2 {field: i32},
    Var3(i32),
}
查看生成的代码
impl ::core::convert::From<EnumDto> for Enum {
    fn from(value: EnumDto) -> Enum {
        match value {
            EnumDto::Var1 => Enum::Var1,
            EnumDto::Var2 { field } => Enum::Var2 {
                field: field,
                _f: 321.0,
            },
            EnumDto::Var3(f0) => Enum::Var3(f0, 123.0),
        }
    }
}
impl ::core::convert::Into<EnumDto> for Enum {
    fn into(self) -> EnumDto {
        match self {
            Enum::Var1 => EnumDto::Var1,
            Enum::Var2 { field, _f } => EnumDto::Var2 { field: field },
            Enum::Var3(f0, f1) => EnumDto::Var3(f0),
        }
    }
}

缺失字段和默认值

#[derive(o2o::o2o)]
#[map_owned(EnumDto)]
enum Enum {
    Var1,
    #[ghosts(f: {123.0})]
    Var2 {
        field: i32,
    },
    #[ghosts(1: {321.0})]
    Var3(
        i32,
    )
}

enum EnumDto {
    Var1,
    Var2 {field: i32, f: f32},
    Var3(i32, f32),
}
查看生成的代码
impl ::core::convert::From<EnumDto> for Enum {
    fn from(value: EnumDto) -> Enum {
        match value {
            EnumDto::Var1 => Enum::Var1,
            EnumDto::Var2 { field, f } => Enum::Var2 { field: field },
            EnumDto::Var3(f0, f1) => Enum::Var3(f0),
        }
    }
}
impl ::core::convert::Into<EnumDto> for Enum {
    fn into(self) -> EnumDto {
        match self {
            Enum::Var1 => EnumDto::Var1,
            Enum::Var2 { field } => EnumDto::Var2 {
                field: field,
                f: 123.0,
            },
            Enum::Var3(f0) => EnumDto::Var3(f0, 321.0),
        }
    }
}

映射到原始类型

使用字面量

可以使用字面量产生 FromInto 实现

#[derive(o2o::o2o)]
#[map_owned(i32| _ => panic!("Not supported"))]
enum HttpStatus {
    #[literal(200)]Ok,
    #[literal(201)]Created,
    #[literal(401)]Unauthorized,
    #[literal(403)]Forbidden,
    #[literal(404)]NotFound,
    #[literal(500)]InternalError
}

type StaticStr = &'static str;

#[derive(o2o::o2o)]
#[map_owned(StaticStr| _ => todo!())]
enum Animal {
    #[literal("🐶")] Dog,
    #[literal("🐱")] Cat,
    #[literal("🐵")] Monkey
}
查看生成的代码
impl ::core::convert::From<i32> for HttpStatus {
    fn from(value: i32) -> HttpStatus {
        match value {
            200 => HttpStatus::Ok,
            201 => HttpStatus::Created,
            401 => HttpStatus::Unauthorized,
            403 => HttpStatus::Forbidden,
            404 => HttpStatus::NotFound,
            500 => HttpStatus::InternalError,
            _ => panic!("Not supported"),
        }
    }
}
impl ::core::convert::Into<i32> for HttpStatus {
    fn into(self) -> i32 {
        match self {
            HttpStatus::Ok => 200,
            HttpStatus::Created => 201,
            HttpStatus::Unauthorized => 401,
            HttpStatus::Forbidden => 403,
            HttpStatus::NotFound => 404,
            HttpStatus::InternalError => 500,
        }
    }
}

impl ::core::convert::From<StaticStr> for Animal {
    fn from(value: StaticStr) -> Animal {
        match value {
            "🐶" => Animal::Dog,
            "🐱" => Animal::Cat,
            "🐵" => Animal::Monkey,
            _ => todo!(),
        }
    }
}
impl ::core::convert::Into<StaticStr> for Animal {
    fn into(self) -> StaticStr {
        match self {
            Animal::Dog => "🐶",
            Animal::Cat => "🐱",
            Animal::Monkey => "🐵",
        }
    }
}

使用模式

模式仅用于产生 From 实现

#[derive(o2o::o2o)]
#[from_owned(i32| _ => panic!())]
enum HttpStatusFamily {
    #[pattern(100..=199)] Information,
    #[pattern(200..=299)] Success,
    #[pattern(300..=399)] Redirection,
    #[pattern(400..=499)] ClientError,
    #[pattern(500..=599)] ServerError,
}

type StaticStr = &'static str;

#[derive(o2o::o2o)]
#[from_owned(StaticStr| _ => todo!())]
enum AnimalKind {
    #[pattern("🐶" | "🐱" | "🐵")]
    Mammal,

    #[pattern("🐟")] 
    Fish,
    
    #[pattern("🐛" | "🐜")]
    Insect
}
查看生成的代码
impl ::core::convert::From<i32> for HttpStatusFamily {
    fn from(value: i32) -> HttpStatusFamily {
        match value {
            100..=199 => HttpStatusFamily::Information,
            200..=299 => HttpStatusFamily::Success,
            300..=399 => HttpStatusFamily::Redirection,
            400..=499 => HttpStatusFamily::ClientError,
            500..=599 => HttpStatusFamily::ServerError,
            _ => panic!(),
        }
    }
}

impl ::core::convert::From<StaticStr> for AnimalKind {
    fn from(value: StaticStr) -> AnimalKind {
        match value {
            "🐶" | "🐱" | "🐵" => AnimalKind::Mammal,
            "🐟" => AnimalKind::Fish,
            "🐛" | "🐜" => AnimalKind::Insect,
            _ => todo!(),
        }
    }
}

一起使用字面量和模式

#[derive(o2o::o2o)]
#[map_owned(i32)]
enum HttpStatus {
    #[literal(200)] Ok,
    #[literal(404)] NotFound,
    #[literal(500)] InternalError,
    #[pattern(_)] #[into({f0})] Other(#[from(@)] i32)
}

type StaticStr = &'static str;

#[derive(o2o::o2o)]
#[map_owned(StaticStr)]
enum Animal {
    #[literal("🐶")] Dog,
    #[literal("🐱")] Cat,
    #[literal("🐵")] Monkey,
    #[pattern(_)] #[into({name})] Other{ #[from(@)] name: StaticStr }
}
查看生成的代码
impl ::core::convert::From<i32> for HttpStatus {
    fn from(value: i32) -> HttpStatus {
        match value {
            200 => HttpStatus::Ok,
            404 => HttpStatus::NotFound,
            500 => HttpStatus::InternalError,
            _ => HttpStatus::Other(value),
        }
    }
}
impl ::core::convert::Into<i32> for HttpStatus {
    fn into(self) -> i32 {
        match self {
            HttpStatus::Ok => 200,
            HttpStatus::NotFound => 404,
            HttpStatus::InternalError => 500,
            HttpStatus::Other(f0) => f0,
        }
    }
}

impl ::core::convert::From<StaticStr> for Animal {
    fn from(value: StaticStr) -> Animal {
        match value {
            "🐶" => Animal::Dog,
            "🐱" => Animal::Cat,
            "🐵" => Animal::Monkey,
            _ => Animal::Other { name: value },
        }
    }
}
impl ::core::convert::Into<StaticStr> for Animal {
    fn into(self) -> StaticStr {
        match self {
            Animal::Dog => "🐶",
            Animal::Cat => "🐱",
            Animal::Monkey => "🐵",
            Animal::Other { name } => name,
        }
    }
}

将可失败转换为原始类型

#[literal(...)]#[pattern(...)] 与可错误转换一起工作得很好

type StaticStr = &'static str;

#[derive(o2o::o2o)]
#[try_map_owned(i32, StaticStr| _ => Err("Unrepresentable")?)]
enum HttpStatus {
    #[literal(200)]Ok,
    #[literal(201)]Created,
    #[literal(401)]Unauthorized,
    #[literal(403)]Forbidden,
    #[literal(404)]NotFound,
    #[literal(500)]InternalError
}

#[derive(o2o::o2o)]
#[try_from_owned(i32, StaticStr| _ => Err("Unrepresentable")?)]
enum HttpStatusFamily {
    #[pattern(100..=199)] Information,
    #[pattern(200..=299)] Success,
    #[pattern(300..=399)] Redirection,
    #[pattern(400..=499)] ClientError,
    #[pattern(500..=599)] ServerError,
}
查看生成的代码
impl ::core::convert::TryFrom<i32> for HttpStatus {
    type Error = StaticStr;
    fn try_from(value: i32) -> Result<HttpStatus, StaticStr> {
        Ok(match value {
            200 => HttpStatus::Ok,
            201 => HttpStatus::Created,
            401 => HttpStatus::Unauthorized,
            403 => HttpStatus::Forbidden,
            404 => HttpStatus::NotFound,
            500 => HttpStatus::InternalError,
            _ => Err("Unrepresentable")?,
        })
    }
}
impl ::core::convert::TryInto<i32> for HttpStatus {
    type Error = StaticStr;
    fn try_into(self) -> Result<i32, StaticStr> {
        Ok(match self {
            HttpStatus::Ok => 200,
            HttpStatus::Created => 201,
            HttpStatus::Unauthorized => 401,
            HttpStatus::Forbidden => 403,
            HttpStatus::NotFound => 404,
            HttpStatus::InternalError => 500,
        })
    }
}

impl ::core::convert::TryFrom<i32> for HttpStatusFamily {
  type Error = StaticStr;
  fn try_from(value: i32) -> Result<HttpStatusFamily, StaticStr> {
      Ok(match value {
          100..=199 => HttpStatusFamily::Information,
          200..=299 => HttpStatusFamily::Success,
          300..=399 => HttpStatusFamily::Redirection,
          400..=499 => HttpStatusFamily::ClientError,
          500..=599 => HttpStatusFamily::ServerError,
          _ => Err("Unrepresentable")?,
      })
  }
}

贡献

所有问题、疑问、pull requests 都非常欢迎。

许可证

根据您的选择,受Apache许可证版本2.0或MIT许可证的许可。
除非您明确声明,否则根据Apache-2.0许可证定义的任何有意提交以包含在此crate中的贡献,都应如上所述双许可,无需附加条款或条件。

依赖关系

~220KB