| Crates.io | restructed |
| lib.rs | restructed |
| version | 0.2.2 |
| created_at | 2023-12-21 14:17:49.115587+00 |
| updated_at | 2025-06-22 09:50:31.08928+00 |
| description | Quickly derive subsets of your structs |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1076933 |
| size | 121,351 |
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.
fields() and omit()From<T> implementations between original and generated structsNew features and roadmap are available here on GitHub.
Add restructed to your Cargo.toml:
[dependencies]
restructed = "0.2"
Or run this command in your project directory:
cargo add restructed
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 fieldsUserUpdate struct with Option<String> fields for username and emailFrom implementations for conversions#[view] - Field SubsetsCreates a struct containing a subset of the original fields. Perfect for API responses, database views, or public representations.
Arguments:
| Name | Description | Required | Type | Example |
|---|---|---|---|---|
name |
Name of the generated struct | Yes (first) | Identifier | UserProfile |
fields |
Fields to include | No | List | fields(id, username) |
omit |
Fields to exclude | No | List | omit(password, secret) |
derive |
Traits to derive | No | List | derive(Debug, Clone) |
preset |
Behavior preset to apply | No | String | preset = "read" |
attributes_with |
Attributes to inherit | No | String | attributes_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 WrappersCreates a struct where each field is wrapped in Option<T>. Ideal for partial updates, PATCH endpoints, or optional modifications.
Arguments:
| Name | Description | Required | Type | Example |
|---|---|---|---|---|
name |
Name of the generated struct | Yes (first) | Identifier | UserUpdate |
fields |
Fields to include | No | List | fields(username, email) |
omit |
Fields to exclude | No | List | omit(id, created_at) |
derive |
Traits to derive | No | List | derive(Debug, Serialize) |
preset |
Behavior preset to apply | No | String | preset = "write" |
attributes_with |
Attributes to inherit | No | String | attributes_with = "oai" |
option |
Alternative to Option<T> |
No | Type | option = MaybeUndefined |
skip_serializing_double_option |
Skip serializing None for Option<Option<T>> |
No | Boolean | skip_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 ConfigurationDefines default arguments applied to all generated models. This attribute doesn't generate structs itself but configures other model generators.
Sub-attributes:
base - Non-overridable DefaultsArguments 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 DefaultsArguments 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,
}
Presets apply common configurations automatically:
"none" (default): No special behavior"write" (requires 'openapi' feature): For writable fields
#[oai(read_only)] fieldsMaybeUndefined for patch option type"read" (requires 'openapi' feature): For readable fields
#[oai(write_only)] fieldsMaybeUndefined for patch option typeControl 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)]
#[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()),
};
}
openapi - Poem OpenAPI IntegrationEnables integration with the poem-openapi crate:
MaybeUndefined<T> instead of Option<T> in patch models#[oai(...)] attributes to generated structsread_only/write_only attributes in presetsuse 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,
}
Struct<T>)Contributions for these features are welcome!
See the project repository for license information.