Crates.io | model-mapper |
lib.rs | model-mapper |
version | 0.2.2 |
source | src |
created_at | 2023-10-12 10:37:12.417417 |
updated_at | 2024-07-13 15:52:38.862809 |
description | Derive macro to map between different types |
homepage | |
repository | https://github.com/lasantosr/model-mapper |
max_upload_size | |
id | 1001192 |
size | 22,652 |
This library provides a macro to implement From
and/or TryFrom
trait between types (both enums and structs) without boilerplate.
It also provides a with
module containing some utilities to convert between types that does not implement Into
trait.
#[derive(Clone, Default, Mapper)]
#[mapper(from, ty = Entity)]
pub struct Model {
id: i64,
name: String,
age: Option<i64>,
}
A mapper
attribute is required at type-level and it's optional at field or variant level.
The following attributes are available.
Type level attributes:
ty = PathType
(mandatory): The other type to derive the conversionignore_extra
(optional): Wether to ignore all extra fields (for structs) or variants (for enums) of the other
type *ignore
(optional, multiple): Additional fields (for structs with named fields) or variants (for enums) the
other type has and this one doesn't *
field = String
(mandatory): The field or variant to ignoredefault = Expr
(optional): The default value (defaults to Default::default()
)from
(optional): Wether to derive From
the other type for selfinto
(optional): Wether to derive From
self for the other typetry_from
(optional): Wether to derive TryFrom
the other type for selftry_into
(optional): Wether to derive TryFrom
self for the other typeVariant level attributes:
ignore_extra
(optional): Wether to ignore all extra fields of the other variant (only valid for from and
try_from) *ignore
(optional, multiple): Additional fields of the variant that the other type variant has and this one
doesn't *
field = String
(mandatory): The field or variant to ignoredefault = Expr
(optional): The default value (defaults to Default::default()
)skip
(optional): Wether to skip this variant because the other enum doesn't have it *default = Expr
(optional): If skipped, the default value to populate this variant (defaults to Default::default()
)rename = "OtherVariant"
(optional): To rename this variant on the other enumField level attributes:
skip
(optional): Wether to skip this field because the other type doesn't have it *default = Expr
(optional): If skipped, the default value to populate this field (defaults to Default::default()
)rename = "other_field"
(optional): To rename this field on the other typewith = mod::my_function
(optional): If the field type doesn't implement Into
or TryInto
the other, this
property allows you to customize the behavior by providing a conversion functioninto_with = mod::my_function
(optional): The same as above but only for the into
derivefrom_with = mod::my_function
(optional): The same as above but only for the from
derive* When ignoring or skipping fields or variants it might be required that the enum or the field type implements
Default
in order to properly populate it if no default value is provided.
Attributes can be set directly if only one type is involved in the conversion:
#[mapper(from, into, ty = OtherType, ignore(field = field_1), ignore(field = field_2))]
Or they can be wrapped in a derive
attribute to allow for multiple conversions:
#[mapper(derive(try_from, ty = OtherType, ignore(field = field_1)))]
#[mapper(derive(into, ty = YetAnotherType))]
If multiple conversions are involved, both variant and field level attributes can also be wrapped in a when
attribute
and must set the ty
they refer to:
#[mapper(when(ty = OtherType, try_with = with::try_remove_option))]
#[mapper(when(ty = YetAnotherType, skip))]
pub enum Model {
Empty,
Text(String),
Data {
id: i64,
text: String,
status: Option<i32>,
internal: bool,
},
Unknown,
}
#[derive(Default, Mapper)]
#[mapper(try_from, into, ty = Model, ignore(field = Unknown))]
pub enum Entity {
Empty,
#[mapper(rename = Text)]
Message(String),
#[mapper(ignore(field = internal))]
Data {
id: i64,
#[mapper(rename = "text")]
message: String,
#[mapper(with = with::option)]
#[mapper(try_with = with::try_option)]
status: Option<i16>,
#[mapper(skip, default = true)]
random: bool,
},
#[mapper(skip, default = Model::Unknown)]
#[default]
Error,
}
The macro expansion above would generate something like:
impl From<Entity> for Model {
fn from(other: Entity) -> Self {
match other {
Entity::Empty => Model::Empty,
Entity::Message(m) => Model::Text(Into::into(m)),
Entity::Data {
id: id,
message: message,
status: status,
random: _,
} => Model::Data {
id: Into::into(id),
text: Into::into(message),
status: with::option(status),
internal: Default::default(),
},
Entity::Error => Model::Unknown,
}
}
}
impl TryFrom<Model> for Entity {
type Error = ::anyhow::Error;
fn try_from(other: Model) -> Result<Self, <Self as TryFrom<Model>>::Error> {
Ok(match other {
Model::Empty => Entity::Empty,
Model::Text(t) => Entity::Message(TryInto::try_into(t)?),
Model::Data {
id: id,
text: message,
status: status,
internal: _,
} => Entity::Data {
id: TryInto::try_into(id)?,
message: TryInto::try_into(message)?,
status: with::try_option(status)?,
random: true,
},
Model::Unknown => Default::default(),
})
}
}
There are more examples available in the integration tests.