20 个版本
新版本 0.4.9 | 2024 年 8 月 16 日 |
---|---|
0.4.9-alpha.1 |
|
0.4.2 | 2024 年 3 月 25 日 |
0.3.0 | 2023 年 6 月 4 日 |
#198 在 Rust 模式
4,076 每月下载量
120KB
1K SLoC
Rust 的对象到对象映射器。派生 (Try)From
和 (Try)Into
特性。
简介
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!)Entity
与EntityDto
是相同的(在这个例子中是一个结构体)数据类型。Entity
与EntityDto
拥有完全相同的字段。
如果 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
,#[] fn
,fn() { #![] }
)的属性
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),
}
}
}
映射到原始类型
使用字面量
可以使用字面量产生 From
和 Into
实现
#[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