Expand description
optionize is a procedural macro to generate optionized versions of structs for partial configurations, updates, and builders.
The generated structs wrap their fields in Option<T> (unless explicitly overridden), allowing you to define partial state.
It also generates corresponding implementations for parsing, merging, downgrading, and upgrading to the original struct.
§Table of Contents
§Basic Usage
The simplest use case wraps every field in an Option, allowing you to create partial representations.
use optionize::{optionized, Optionized};
#[optionized]
#[derive(Debug, PartialEq)]
struct Wrap {
_0: i32,
_1: i32,
}
let wrap = Wrap {
_0: 0,
_1: 1,
};
// The generated struct is named `WrapOptional` by default.
let wrap_optional = WrapOptional {
_0: Some(10),
_1: Some(11),
};§Struct-level Attributes
§Renaming the generated struct
By default, the generated struct’s name appends Optional to the original name. You can customize this using name = "...".
{} acts as a placeholder for the original struct name.
use optionize::optionized;
#[optionized]
#[optionize(name = "{}Builder")]
struct Config {
port: u16,
}
let builder = ConfigBuilder { port: Some(8080) };§Using a user-defined object struct
If you want to hand-write the optionized struct, use object = "..." to point the macro at your type.
The macro will only generate the trait implementations and will expect your object struct to match the
fields it would normally generate (including #[optionize(name = ...)], flatten, skip, and nest rules).
use optionize::{optionized, Optionized};
#[optionized]
#[optionize(object = "UserOptional", partial(upgradable))]
#[derive(Debug, PartialEq, Clone)]
struct User {
id: u32,
#[optionize(flatten)]
active: bool,
}
#[derive(Debug, PartialEq, Clone)]
struct UserOptional {
id: Option<u32>,
active: bool,
}
let o = UserOptional { id: Some(1), active: true };
assert_eq!(o.upgrade().unwrap(), User { id: 1, active: true });§Overriding derived attributes
The generated struct inherits all attributes (e.g., #[derive(...)]) from the original struct by default.
Using attrs(...) allows you to completely override the attributes on the generated struct.
use optionize::optionized;
#[optionized]
// The generated struct will derive Clone and Debug, but NOT PartialEq.
#[optionize(attrs(derive(Clone, Debug)))]
#[derive(Debug, PartialEq)]
struct User {
id: u32,
}§Upgrading and the partial attribute
To support converting the partial struct back into the full struct, you must specify partial(upgradable).
This implements the Optionized trait, providing .validate() and .upgrade() methods.
use optionize::{optionized, Optionized};
#[optionized]
#[optionize(partial(upgradable))]
#[derive(Debug, PartialEq)]
struct Data {
value: i32,
}
let partial = DataOptional { value: Some(42) };
assert_eq!(partial.upgrade().unwrap().value, 42);§Generics and the marked attribute
If you use skip on a field that uses a generic type, that generic type becomes unused in the generated struct, resulting in a compilation error.
To fix this, use marked to inject a PhantomData field that consumes the generic type.
use optionize::{optionized, Optionized};
use std::marker::PhantomData;
#[optionized]
#[optionize(partial(marked(name = _marker), upgradable))]
#[derive(Debug)]
struct Generic<D: Clone + Default> {
#[optionize(skip)]
data: D,
}
let generic_optional = GenericOptional::<i32> { _marker: PhantomData };
assert!(generic_optional.validate().is_ok());Note: For unit structs, using marked changes the generated struct to a tuple struct, or a named struct if marked(name = ...) is provided.
§Field-level Attributes
§Renaming fields
Use name to change a field’s name in the generated struct. {} acts as a placeholder for the original name or index.
use optionize::optionized;
#[optionized]
struct RenameExample {
#[optionize(name = "renamed_value")]
value: i32,
}§Overriding field attributes
Similar to struct-level attrs, you can override attributes on specific fields.
use optionize::optionized;
#[optionized]
struct FieldAttrExample {
#[optionize(attrs(doc = "This is a custom doc string for the partial struct field"))]
value: i32,
}§Flattening fields
flatten prevents the macro from wrapping the field’s type in Option<T>. The field will have the exact same type in the generated struct.
use optionize::{optionized, Optionized};
#[optionized]
#[derive(Debug, PartialEq)]
struct FlattenExample {
#[optionize(flatten)]
id: u32,
name: String,
}
let partial = FlattenExampleOptional {
id: 1, // Notice `id` is `u32`, not `Option<u32>`
name: Some("Alice".into()),
};§Skipping fields during upgrade
skip removes the field entirely from the generated struct.
Because a skipped field cannot be supplied during an upgrade, the struct must be upgradable.
You can optionally provide a default value via upgrade = expr; otherwise, it uses Default::default().
use optionize::{optionized, Optionized};
#[optionized]
#[optionize(partial(upgradable))]
#[derive(Debug, PartialEq)]
struct SkipExample {
#[optionize(skip(upgrade = 123))]
id: i32,
}
let partial = SkipExampleOptional {};
assert_eq!(partial.upgrade().unwrap().id, 123);§Nesting other optionized structs
For complex configurations, you can delegate optionization to nested types using nest.
use optionize::{optionized, Optionized, PartialOptionized};
#[optionized]
#[derive(Debug, Clone, PartialEq)]
#[optionize(partial(upgradable))]
struct Inner {
x: u32,
}
#[optionized]
#[derive(Debug, Clone)]
struct Outer {
#[optionize(nest = "InnerOptional")]
a: Inner,
}
let mut outer = OuterOptional {
a: Some(InnerOptional { x: Some(1) }),
};§Trait Operations
The macro generates implementations for PartialOptionized and (optionally) Optionized. Extension methods are provided via Optionizable.
§Downgrading & Loading
You can convert a full struct into its partial version (downgrade), or load partial data into a full struct (load).
use optionize::{optionized, Optionizable};
#[optionized]
#[derive(Debug, PartialEq)]
struct User {
id: u32,
name: String,
}
let mut user = User { id: 1, name: "Alice".into() };
// Downgrade to partial
let partial = user.downgrade();
assert_eq!(partial.id, Some(1));
// Load from partial
let mut updated_user = User { id: 2, name: "Bob".into() };
updated_user.load(partial);
assert_eq!(updated_user.name, "Alice");§Patching & Merging
Combine partial states using .merge(other) or apply them to a full state using .patch(subject).
use optionize::{optionized, PartialOptionized};
#[optionized]
struct Merging { a: u32, b: u32 }
let mut p1 = MergingOptional { a: Some(1), b: None };
let p2 = MergingOptional { a: Some(2), b: Some(3) };
p1.merge(p2);
assert_eq!(p1.a, Some(2)); // Overwritten
assert_eq!(p1.b, Some(3)); // Filled§Validation & Upgrading
Call .validate() to ensure all required fields are present. Call .upgrade() to convert it to the original type.
use optionize::{optionized, Optionized};
#[optionized]
#[optionize(partial(upgradable))]
struct Validated { req: String }
let p = ValidatedOptional { req: None };
assert!(p.validate().is_err());Structs§
Enums§
Traits§
- Optionizable
- Provides extension methods on the original subject struct to easily work with its
PartialOptionizedcounterpart without having to import and specify the partial type. - Optionized
- Implemented on an optionized struct (if marked
upgradable), allowing validation of its completeness and a direct upgrade to the original subject struct. - Partial
Optionized - Represents the relationship between a generated optionized struct and its target original struct. Allows extracting partial data from a full struct, applying partial data to a full struct, and merging two partial structs together.
Attribute Macros§
- optionized
- Generates an optionized version of a struct, replacing its fields with
Option<T>where applicable, and implements conversion and merge logic for partial updates and builders.