# Model Mapper
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.
## Examples
The most common use case for this crate is to map between domain entities on services and externally-faced models or DTOs.
```rs
#[derive(Mapper)]
#[mapper(from, ty = Entity)]
pub struct Model {
id: i64,
name: String,
}
```
The macro expansion above would generate something like:
```rs
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:
```rs
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:
```rs
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](./model-mapper/examples/).
## 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 conversion
- `from` _(optional)_: Whether to derive `From` the other type for self
- `custom` _(optional)_: Derive a custom function instead of the trait
- `custom = from_other` _(optional)_: Derive a custom function instead of the trait, with the given name
- `into` _(optional)_: Whether to derive `From` self for the other type
- `custom` _(optional)_: Derive a custom function instead of the trait
- `custom = from_other` _(optional)_: Derive a custom function instead of the trait, with the given name
- `try_from` _(optional)_: Whether to derive `TryFrom` the other type for self
- `custom` _(optional)_: Derive a custom function instead of the trait
- `custom = from_other` _(optional)_: Derive a custom function instead of the trait, with the given name
- `try_into` _(optional)_: Whether to derive `TryFrom` self for the other type
- `custom` _(optional)_: Derive a custom function instead of the trait
- `custom = 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 name
- `ty = bool` _(optional)_: The field type, mandatory for `into` and `try_into` if no default value is provided
- `default` _(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 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 enum
- `add` _(optional, multiple)_: Additional fields of the variant that the other type variant has and this one
doesn't **¹**
- `field = other_field` _(mandatory)_: The field name
- `ty = bool` _(optional)_: The field type, mandatory for `into` and `try_into` if no default value is provided
- `default` _(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 instead
- `skip` _(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 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 type
- `skip` _(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 instead
- `with = 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 function
- `into_with = mod::my_function` _(optional)_: The same as above but only for the `into` or `try_into` derives
- `from_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.
### Multiple derives
When deriving conversions for a single type, attributes can be set directly:
```rs
#[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:
```rs
#[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:
```rs
#[mapper(when(ty = OtherType, with = TryIntoMapper::try_map_removing_option))]
#[mapper(when(ty = YetAnotherType, skip(default)))]
```