simple_dto_mapper_derive

Crates.iosimple_dto_mapper_derive
lib.rssimple_dto_mapper_derive
version0.1.1
created_at2025-08-09 03:52:52.352646+00
updated_at2025-08-09 05:04:38.651074+00
descriptionA derive macro for mapping DTOs with rename/transform/skip support
homepage
repositoryhttps://github.com/sukjaelee/simple_dto_mapper_derive.git
max_upload_size
id1787558
size67,549
lee suk-jae (sukjaelee)

documentation

README

simple_dto_mapper_derive

simple_dto_mapper_derive provides a custom derive macro DtoFrom that generates impl From<Source> for Target to map model structs into DTOs. It focuses on a small, explicit set of attributes for clarity and reliability.

Features

  • Source struct: #[dto(from = Type)] (required)
  • Field rename: #[dto(rename = "orig_name")]
  • Custom transform: #[dto(transform_fn = path::to::function)] (owned in → owned out)
  • Skip with default: #[dto(skip)]
  • Infallible convert: #[dto(into)] (uses From<Src> for Dst)
  • Clear diagnostics: unknown/duplicate/conflicting attributes produce span-accurate compile errors (see tests/ui)

Mapping Rules (at a glance)

  • Default (owned move)
    Same name & same typetarget = source.field
    “Compatible type” means:

    • Identical type, or
    • #[dto(into)] where From<SourceFieldType> for FieldType exists, or
    • #[dto(transform_fn = ...)] provides an explicit conversion
  • Field attributes

    • #[dto(rename = "orig_name")] — read from another source field name
    • #[dto(transform_fn = path)] — call path(source.orig_name) before assignment
    • #[dto(skip)] — initialize with Default::default()
    • #[dto(into)] — call Into::into(source.orig_name) (requires From)
  • Struct attribute (required)
    #[dto(from = Type)] — specify the source struct.

Usage

use simple_dto_mapper_derive::DtoFrom;

mod types {
    #[derive(Debug)]
    pub struct User {
        pub id: String,
        pub name: String,
        pub age: u32,
        pub password: String, // intentionally not mapped
        pub status: SourceStatus,
    }

    #[derive(Debug, Clone)]
    pub enum SourceStatus { Active, Inactive }

    #[derive(Debug, Clone)]
    pub enum DtoStatus { Active, Inactive }

    // For `#[dto(into)]`
    impl From<SourceStatus> for DtoStatus {
        fn from(s: SourceStatus) -> Self {
            match s { SourceStatus::Active => Self::Active, SourceStatus::Inactive => Self::Inactive }
        }
    }

    // For `#[dto(transform_fn = ...)]`
    pub fn mask_name(name: String) -> String {
        let ch = name.chars().next().unwrap_or('*');
        format!("{ch}***")
    }
}

#[derive(DtoFrom, Debug)]
#[dto(from = types::User)]
struct UserDto {
    // `rename` + `transform_fn`
    #[dto(rename = "name", transform_fn = types::mask_name)]
    display_name: String,

    // Direct mapping
    age: u32,

    // skip: default initialize
    #[dto(skip)]
    note: Option<String>,

    // `into`: uses `From<SourceStatus> for DtoStatus`
    #[dto(into)]
    status: types::DtoStatus,
}

fn main() {
    let user = types::User {
        id: "u1".into(),
        name: "Alice".into(),
        age: 30,
        password: "secret".into(),
        status: types::SourceStatus::Active,
    };
    let dto: UserDto = user.into();
    println!("{dto:?}");
}

Collections & Option

Collections and Option do not auto-convert inner elements. Use a transform_fn helper or implement From<Vec<T>> for Vec<U> if you prefer #[dto(into)].

// same type, no transform needed
pub keywords: Vec<String>,

// Vec<SourceTag> → Vec<DtoTag> using a transform function
#[dto(rename = "labels", transform_fn = types::vec_into::<types::SourceTag, types::DtoTag>)]
pub tags: Vec<DtoTag>,

// Option<SourceAuthor> → Option<DtoAuthor>
#[dto(transform_fn = types::opt_into::<types::SourceAuthor, types::DtoAuthor>)]
pub author: Option<DtoAuthor>,

Installation

Add this to your Cargo.toml:

[dependencies]
simple_dto_mapper_derive = "0.1.1"

Diagnostics & Limitations

  • Named-field structs only (tuple/unit structs & enums are not supported)
  • Owned-only mapping (impl From<Source> for Target)
  • transform_fn must be FnOnce(SrcField) -> DstField
  • into requires From<SrcField> for DstField
  • skip requires Default
  • Clear errors for unknown/duplicate/conflicting attributes; see tests/ui

License

Licensed under the MIT license.

Commit count: 0

cargo fmt