Skip to content

nik-rev/subdef

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

subdef

crates.io docs.rs license msrv github

This crate provides an attribute macro #[subdef] - it lets you define nested structs and enums "inline", to keep all definitions in a single place and reduce how much scrolling you have to do

Crates like subdef are commonly used to model nested APIs 1:1 using serde

[dependencies]
subdef = "0.1"

It successor to crates like nestify, nest_struct and structstruck.

What distinguishes subdef from the others is that types that have #[subdef] applied to them can be fully formatted by rustfmt.

Usage

Apply #[subdef] to your type to be able to define inline types in individual fields

#[subdef]
struct UserProfile {
    name: String,
    address: [_; {
        struct Address {
            street: String,
            city: String
        }
    }],
    friends: [Vec<_>; {
        struct Friend {
            name: String
        }
    }],
    status: [_; {
        enum Status {
            Online,
            Offline,
            Idle
        }
    }]
}

Expansion:

struct UserProfile {
    name: String,
    address: Address,
    friends: Vec<Friend>,
    status: Status,
}
struct Address {
    street: String,
    city: String,
}
struct Friend {
    name: String,
}
enum Status {
    Online,
    Offline,
    Idle,
}

How it works

Fields on types marked with #[subdef] can have the type [Type; { Item }] where Type is the actual type of the field, and Item is the struct or enum.

The Type can contain _, which infers to the name of the Item. In the above example:

  • The address field contains _, which infers to be Address.
  • The friends field contains _, which infers to be Friend, so Vec<_> is inferred to Vec<Friend>

You can apply #[subdef] to enums:

#[subdef]
pub enum One {
    Two([_; { pub struct Two; }])
}

Inline types can contain fields that have inline types themselves:

#[subdef]
struct One {
    two: [_; {
        struct Two {
            three: [_; {
                struct Three;
            }]
        }
    }]
}

Admittedly, the syntax is a little strange, but that's a small price to pay for the convenience of automatic formatting by rustfmt!

Propagate attributes

Give attributes to subdef(...), and they will be propagated recursively to all inline types

#[subdef(derive(Serialize, Deserialize))]
struct SystemReport {
    report_id: Uuid,
    kind: [_; {
        pub enum ReportKind {
            Initial,
            Heartbeat,
            Shutdown,
        }
    }],
    application_config: [_; {
        struct ApplicationConfig {
            version: String,
            container_runtime: String,

            flags: [_; {
                struct Flags {
                    is_admin: bool,
                    is_preview_mode: bool,
                    telemetry_enabled: bool,
                }
            }],
            components: [Vec<_>; {
                struct Component {
                    name: String,
                    version: String,
                    maintainer: Option<String>,
                    target_platform: String,
                }
            }],
        }
    }],
}

Expands to this:

#[derive(Serialize, Deserialize)]
struct SystemReport {
    report_id: Uuid,
    kind: ReportKind,
    application_config: ApplicationConfig
}

#[derive(Serialize, Deserialize)]
pub enum ReportKind {
    Initial,
    Heartbeat,
    Shutdown
}

#[derive(Serialize, Deserialize)]
struct ApplicationConfig {
    version: String,
    container_runtime: String,
    flags: Flags,
    components: Vec<Component>
}

#[derive(Serialize, Deserialize)]
struct Flags {
    is_admin: bool,
    is_preview_mode: bool,
    telemetry_enabled: bool
}

#[derive(Serialize, Deserialize)]
struct Component {
    name: String,
    version: String,
    maintainer: Option<String>,
    target_platform: String
}

Fine-tune propagation

Note: This is an advanced section, most use cases won't need this feature

You can attach labels to each attribute:

#[subdef(
    label1 = cfg(not(windows)),
    label2 = derive(Serialize, Deserialize)
)]
struct SystemReport { /* ... */ }

These are the fine-tuning attributes that you can use:

  • #[subdef(skip(label1, label2))] to skip applying the attribute to the type
  • #[subdef(skip_recursively(label1, label2))] to recursively skip applying the attribute to the type
  • #[subdef(apply(label1, label2))] to apply the attribute, overriding any previous #[subdef(skip_recursively)]
  • #[subdef(apply_recursively(label1, label2))] to recursively apply the attribute, overriding any previous #[subdef(skip_recursively)]

Example usage of these fine-tuning attributes:

#[subdef(
    debug = derive(Debug),
    eq = derive(PartialEq, Eq)
)]
#[subdef(skip(debug), skip(eq))]
struct Order {
    billing_info: [_; {
        #[subdef(skip_recursively(eq))]
        struct BillingInfo {
            payment_transaction: [_; {
                struct TransactionData {
                    amount_paid_cents: u32,
                }
            }],
        }
    }],
    shipping_details: [_; {
        #[subdef(apply_recursively(eq))]
        struct ShippingDetails {
            confirmation_status: [_; {
                #[subdef(apply(debug))]
                struct DetailsConfirmed;
            }],
        }
    }],
}

Expansion:

struct Order {
    billing_info: BillingInfo,
    shipping_details: ShippingDetails,
}

#[derive(Debug)]
struct TransactionData {
    amount_paid_cents: u32,
}

#[derive(Debug)]
struct BillingInfo {
    payment_transaction: TransactionData,
}

#[derive(PartialEq, Eq, Debug)]
struct DetailsConfirmed;

#[derive(PartialEq, Eq, Debug)]
struct ShippingDetails {
    confirmation_status: DetailsConfirmed,
}

About

Expressive attribute macro to define nested structures

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

No packages published