Crates.io | model-mapper |
lib.rs | model-mapper |
version | 0.5.0 |
source | src |
created_at | 2023-10-12 10:37:12.417417 |
updated_at | 2024-09-02 18:29:38.651603 |
description | Derive macro to map between different types |
homepage | |
repository | https://github.com/lasantosr/model-mapper |
max_upload_size | |
id | 1001192 |
size | 62,920 |
This library provides a macro to implement functions to convert 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.
As long as you don't use the with
module (disable default features) and don't derive try_into
or try_form
, this
lib can be used in #![no_std]
crates.
The most common use case for this crate is to map between domain entities on services and externally-faced models or DTOs.
#[derive(Mapper)]
#[mapper(from, ty = Entity)]
pub struct Model {
id: i64,
name: String,
}
The macro expansion above would generate something like:
impl From<Entity> for Model {
fn from(Entity { id, name }: Entity) -> Self {
Self {
id: Into::into(id),
name: Into::into(name),
}
}
}
Because types doesn't always fit like a glove, you can provide additional fields on runtime, at the cost of not being
able to use the From
trait:
pub mod service {
pub struct UpdateUserInput {
pub user_id: i64,
pub name: Option<String>,
pub surname: Option<String>,
}
}
#[derive(Mapper)]
#[mapper(
into(custom = "into_update_user"),
ty = service::UpdateUserInput,
add(field = user_id, ty = i64),
add(field = surname, default(value = None))
)]
pub struct UpdateProfileRequest {
pub name: String,
}
Would generate something like:
impl UpdateProfileRequest {
/// Builds a new [service::UpdateUserInput] from a [UpdateProfileRequest]
pub fn into_update_user(self, user_id: i64) -> service::UpdateUserInput {
let UpdateProfileRequest { name } = self;
service::UpdateUserInput {
user_id,
surname: None,
name: Into::into(name),
}
}
}
Other advanced use cases are available on the examples folder.
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 conversionfrom
(optional): Whether to derive From
the other type for self
custom
(optional): Derive a custom function instead of the traitcustom = from_other
(optional): Derive a custom function instead of the trait, with the given nameinto
(optional): Whether to derive From
self for the other type
custom
(optional): Derive a custom function instead of the traitcustom = from_other
(optional): Derive a custom function instead of the trait, with the given nametry_from
(optional): Whether to derive TryFrom
the other type for self
custom
(optional): Derive a custom function instead of the traitcustom = from_other
(optional): Derive a custom function instead of the trait, with the given nametry_into
(optional): Whether to derive TryFrom
self for the other type
custom
(optional): Derive a custom function instead of the traitcustom = from_other
(optional): Derive a custom function instead of the trait, with the given nameadd
(optional, multiple): Additional fields (for structs with named fields) or variants (for enums) the
other type has and this one doesn't ¹
field = other_field
(mandatory): The field or variant namety = bool
(optional): The field type, mandatory for into
and try_into
if no default value is provideddefault
(optional): The field or variant will be populated using Default::default()
(mandatory for enums,
with or without value)
value = true
(optional): The field or variant will be populated with the given expression insteadignore_extra
(optional): Whether to ignore all extra fields (for structs) or variants (for enums) of the other
type ²Variant level attributes:
rename = OtherVariant
(optional): To rename this variant on the other enumadd
(optional, multiple): Additional fields of the variant that the other type variant has and this one
doesn't ¹
field = other_field
(mandatory): The field namety = bool
(optional): The field type, mandatory for into
and try_into
if no default value is provideddefault
(optional): The field or variant will be populated using Default::default()
value = true
(optional): The field or variant will be populated with the given expression insteadskip
(optional): Whether to skip this variant because the other enum doesn't have it
default
(mandatory): The field or variant will be populated using Default::default()
value = get_default_value()
(optional): The field or variant will be populated with the given expression insteadignore_extra
(optional): Whether to ignore all extra fields of the other variant (only valid for from and
try_from) ²Field level attributes:
rename = other_name
(optional): To rename this field on the other typeskip
(optional): Whether to skip this field because the other type doesn't have it
default
(optional): The field or variant will be populated using Default::default()
value = get_default_value()
(optional): The field or variant will be populated with the given expression insteadwith = 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
or try_into
derivesfrom_with = mod::my_function
(optional): The same as above but only for the from
or try_from
derives¹ When providing additional fields without defaults, the From
and TryFrom
traits can't be derived and a
custom function will be required instead. When deriving into
or try_into
, the ty
must be provided as well.
² When ignoring fields or variants it might be required that the enum or the struct implements Default
in order to properly populate it.
When deriving conversions for a single type, attributes can be set directly:
#[mapper(from, into, ty = OtherType, add(field = field_1, default), add(field = field_2, default))]
But we can also derive conversions for multiple types by wrapping the properties on a derive
attribute:
#[mapper(derive(try_into, ty = OtherType, add(field = field_1, default)))]
#[mapper(derive(from, 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, with = TryIntoMapper::try_map_removing_option))]
#[mapper(when(ty = YetAnotherType, skip(default)))]