Skip to main content

Crate optionize

Crate optionize 

Source
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§

ErrorCollection
TypeInfo

Enums§

Error
FieldInfo

Traits§

Optionizable
Provides extension methods on the original subject struct to easily work with its PartialOptionized counterpart 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.
PartialOptionized
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.