Crate restructed

Source
Expand description

§restructed


A quick and easy way to create derivative models of your existing types without repeating yourself. Reduce boilerplate and automatically generate related structs with custom field subsets and transformations.

§Features

  • Reduce boilerplate: Generate multiple related structs from a single definition
  • Flexible field selection: Include or exclude specific fields with fields() and omit()
  • Automatic trait generation: From<T> implementations between original and generated structs
  • Derive support: Apply derives to generated structs
  • Multiple model types: Views, patches, and custom transformations

New features and roadmap are available here on GitHub.

§Installation

Add restructed to your Cargo.toml:

[dependencies]
restructed = "0.2"

Or run this command in your project directory:

cargo add restructed

§Quick Start

Add the derive macro to your struct:

use restructed::Models;

#[derive(restructed::Models)]
struct User {
    id: i32,
    username: String,
    email: String,
    password: String,
}

Then add attributes for each model you want to generate:

#[derive(restructed::Models)]
#[view(UserProfile, omit(password))]           // Subset without sensitive fields
#[patch(UserUpdate, fields(username, email))]  // Optional fields for updates
struct User {
    id: i32,
    username: String,
    email: String,
    password: String,
}

This generates:

  • UserProfile struct with id, username, and email fields
  • UserUpdate struct with Option<String> fields for username and email
  • Automatic From implementations for conversions

§Model Types

§#[view] - Field Subsets

Creates a struct containing a subset of the original fields. Perfect for API responses, database views, or public representations.

Arguments:

NameDescriptionRequiredTypeExample
nameName of the generated structYes (first)IdentifierUserProfile
fieldsFields to includeNoListfields(id, username)
omitFields to excludeNoListomit(password, secret)
deriveTraits to deriveNoListderive(Debug, Clone)
presetBehavior preset to applyNoStringpreset = "read"
attributes_withAttributes to inheritNoStringattributes_with = "all"

Note: Use either fields OR omit, not both.

Example:

#[derive(Clone, restructed::Models)]
#[view(UserProfile, omit(id, password))]
struct User {
    id: i32,           // Not in UserProfile
    username: String,  // In UserProfile
    email: String,     // In UserProfile
    bio: String,       // In UserProfile
    password: String,  // Not in UserProfile
}

// Usage
let user = User {
  id: 1,
  username: "alice".to_string(),
  email: "alice@example.com".to_string(),
  bio: "Rustacean".to_string(),
  password: "super_secret".to_string(),
};
let profile: UserProfile = user.into();

§#[patch] - Optional Field Wrappers

Creates a struct where each field is wrapped in Option<T>. Ideal for partial updates, PATCH endpoints, or optional modifications.

Arguments:

NameDescriptionRequiredTypeExample
nameName of the generated structYes (first)IdentifierUserUpdate
fieldsFields to includeNoListfields(username, email)
omitFields to excludeNoListomit(id, created_at)
deriveTraits to deriveNoListderive(Debug, Serialize)
presetBehavior preset to applyNoStringpreset = "write"
attributes_withAttributes to inheritNoStringattributes_with = "oai"
optionAlternative to Option<T>NoTypeoption = MaybeUndefined
skip_serializing_double_optionSkip serializing None for Option<Option<T>>NoBooleanskip_serializing_double_option = true

Example:

#[derive(Clone, restructed::Models)]
#[patch(UserUpdate, omit(id))]
struct User {
    id: i32,                    // Not in UserUpdate
    username: String,           // Option<String> in UserUpdate
    email: String,              // Option<String> in UserUpdate
    bio: Option<String>,        // Option<Option<String>> in UserUpdate
}

// Usage
let update = UserUpdate {
    username: Some("new_username".to_string()),
    email: None,  // Don't update email
    bio: Some(Some("New bio".to_string())),  // Set bio
};

§#[model] - Base Configuration

Defines default arguments applied to all generated models. This attribute doesn’t generate structs itself but configures other model generators.

Sub-attributes:

§base - Non-overridable Defaults

Arguments that are always applied and cannot be overridden by individual models.

#[model(base(derive(Debug, Clone)))]  // All models MUST derive Debug and Clone
§defaults - Overridable Defaults

Arguments applied only when not specified by individual models.

#[model(defaults(derive(Serialize), preset = "read"))]  // Applied unless overridden

Example:

#[derive(restructed::Models)]
#[model(
    base(derive(Debug)),                     // All models derive Debug
    defaults(derive(Clone), preset = "read") // Default unless overridden
)]
#[view(UserView)]                           // Inherits Debug + Clone + preset="read"
#[patch(UserPatch, preset = "write")]       // Inherits Debug + Clone, overrides preset
struct User {
    id: i32,
    username: String,
    password: String,
}

§Advanced Features

§Presets

Presets apply common configurations automatically:

  • "none" (default): No special behavior
  • "write" (requires ‘openapi’ feature): For writable fields
    • Removes #[oai(read_only)] fields
    • Uses MaybeUndefined for patch option type
  • "read" (requires ‘openapi’ feature): For readable fields
    • Removes #[oai(write_only)] fields
    • Uses MaybeUndefined for patch option type

§Attribute Inheritance

Control which attributes are copied to generated structs:

  • "none" (default): No attributes copied

  • "oai" (requires ‘openapi’ feature): Copy OpenAPI attributes

  • "deriveless": Copy all attributes except derives

  • "all": Copy all attributes, even dervies but they’ll need to be on their own line (See below example) i.e.

    #[derive(restructed::Models)]
    #[model(defaults(attributes_with = "all"))]
    #[derive(Clone)]

§Complete Example

#[derive(restructed::Models, Clone)]
#[view(UserProfile, omit(password, internal_id))]
#[view(UserSummary, fields(id, username))]
#[patch(UserUpdate, omit(id, internal_id, created_at), preset = "write")]
struct User {
    id: i32,
    internal_id: String,
    username: String,
    email: String,
    password: String,
    created_at: String,
}

fn example_usage() {
    let user = User {
        id: 1,
        internal_id: "internal_123".to_string(),
        username: "alice".to_string(),
        email: "alice@example.com".to_string(),
        password: "secret".to_string(),
        created_at: "2024-01-01".to_string(),
    };

    // Convert to different views
    let profile: UserProfile = user.clone().into();
    let summary: UserSummary = user.clone().into();

    // Create update struct
    let update = UserUpdate {
        username: Some("new_alice".to_string()),
        email: None,  // Don't update
        password: Some("new_secret".to_string()),
    };
}

§Feature Flags

§openapi - Poem OpenAPI Integration

Enables integration with the poem-openapi crate:

  • Use MaybeUndefined<T> instead of Option<T> in patch models
  • Copy #[oai(...)] attributes to generated structs
  • Respect read_only/write_only attributes in presets
use restructed::Models;

#[derive(poem_openapi::Object, Models)]
#[oai(skip_serializing_if_is_none, rename_all = "camelCase")]
#[model(
    base(derive(poem_openapi::Object, Debug)),
    defaults(preset = "read")
)]
#[patch(UserUpdate, preset = "write")]
#[view(UserProfile)]
#[view(UserNames, fields(username, name, surname))]
pub struct User {
    #[oai(read_only)]
    pub id: u32,

    #[oai(validator(min_length = 3, max_length = 16, pattern = r"^[a-zA-Z0-9_]*$"))]
    pub username: String,

    #[oai(validator(min_length = 5, max_length = 1024), write_only)]
    pub password: String,

    #[oai(validator(min_length = 2, max_length = 16, pattern = r"^[a-zA-Z\s]*$"))]
    pub name: Option<String>,

    #[oai(validator(min_length = 2, max_length = 16, pattern = r"^[a-zA-Z\s]*$"))]
    pub surname: Option<String>,

    #[oai(read_only)]
    pub joined: u64,
}

§Limitations

  • Generic types: Currently doesn’t support generic structs or enums (e.g., Struct<T>)
  • Enum support: Only works with structs, not enums

Contributions for these features are welcome!

§License

See the project repository for license information.

Derive Macros§

Models