Expand description
§Model Mapper
A powerful Rust macro to generate boilerplate-free declarations of From, Into, TryFrom, and TryInto traits
for converting between structs and enums.
It is designed to handle common patterns like detailed DTOs to internal entities, handling optional fields, nested collections, and even disparate generic types.
§Features
- Zero Boilerplate: Automatically implements
From,Into,TryFrom, andTryInto. - Flexible Mapping: Handle renamed fields, skipped fields, and additional fields.
- Custom Logic: Inject custom conversion logic for specific fields using functions or expressions.
- Generics Support: Seamless mapping between generic types with different parameters.
- Multiple Targets: Map a single type to multiple other types with conditional configurations.
- Nested Mapping: Built-in support for mapping inner values within Option, iterators, and maps.
no_stdcompatible: Works inno_stdenvironments (with default features disabled).
§Quick Start
The most common use case is mapping between domain entities and 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.
§Detailed Usage
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 deriveFromthe other type for selfcustom(optional): Derive a custom function instead of the traitcustom = from_other(optional): Derive a custom function instead of the trait, with the given name
into(optional): Whether to deriveFromself for the other typecustom(optional): Derive a custom function instead of the traitcustom = from_other(optional): Derive a custom function instead of the trait, with the given name
try_from(optional): Whether to deriveTryFromthe other type for selfcustom(optional): Derive a custom function instead of the traitcustom = from_other(optional): Derive a custom function instead of the trait, with the given name
try_into(optional): Whether to deriveTryFromself for the other typecustom(optional): Derive a custom function instead of the traitcustom = from_other(optional): Derive a custom function instead of the trait, with the given name
add(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 forintoandtry_intoif no default value is provideddefault(optional): The field or variant will be populated usingDefault::default()(mandatory for enums, with or without value)value = true(optional): The field or variant will be populated with the given expression instead
ignore_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 forintoandtry_intoif no default value is provideddefault(optional): The field or variant will be populated usingDefault::default()value = true(optional): The field or variant will be populated with the given expression instead
skip(optional): Whether to skip this variant because the other enum doesn’t have itdefault(mandatory): The field or variant will be populated usingDefault::default()value = get_default_value()(optional): The field or variant will be populated with the given expression instead
ignore_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 itdefault(optional): The field or variant will be populated usingDefault::default()value = get_default_value()(optional): The field or variant will be populated with the given expression instead
with = mod::my_function(optional): If the field type doesn’t implementIntoorTryIntothe 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 theintoortry_intoderivesfrom_with = mod::my_function(optional): The same as above but only for thefromortry_fromderives
-
Additional hints on how to map fields:
opt(optional): The field is anOptionand the inner value shall be mapped ³iter(optional): The field is an iterator and the inner value shall be mapped ³map(optional): The field is a hashmap-like iterator and the inner value shall be mapped ³with = mod::my_function(optional): If the field type doesn’t implementIntoorTryIntothe other, this property allows you to customize the behavior by providing a conversion functionfrom_with = mod::my_function(optional): The same as above but only for thefromortry_fromderivesfrom_with = mod::my_function(optional): The same as above but only for thefromortry_fromderives
¹ 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.
³ Hints can be nested, for example: opt(vec), vec(opt(with = "my_custom_fn"))
§Multiple derives
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))]
struct MyStruct;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))]
struct MyStruct;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 = ToString::to_string))]
#[mapper(when(ty = YetAnotherType, skip(default)))]
struct MyStruct;