| Crates.io | optifier |
| lib.rs | optifier |
| version | 0.1.0-beta.3 |
| created_at | 2023-04-09 09:32:47.519703+00 |
| updated_at | 2026-01-15 23:55:01.5329+00 |
| description | Rust macros for deriving optional types |
| homepage | https://github.com/kkafar/optifier |
| repository | https://github.com/kkafar/optifier |
| max_upload_size | |
| id | 834054 |
| size | 20,535 |
Proc macro crate: Partial derive
[!note] This crate is early development phase. I use it only in my personal projects and update it as needed.
Derive Partial on a struct Foo to generate a new struct named FooPartial
where every field type is wrapped in Option<T> unless it is already an Option<T>.
Only structs with named fields are accepted; tuple and unit structs are not supported yet.
For every FooPartial the macro also generates:
merge method to combine two partial values, andTryFrom<FooPartial> for Foo implementation with a dedicated error type.Example:
use optifier;
#[derive(optifier::Partial)]
#[optifier::partial_derive(Debug, Clone)]
pub struct Foo {
a: i32,
b: Option<String>,
pub c: Vec<u8>,
}
expands to:
#[derive(Debug, Clone)]
pub struct FooPartial {
a: Option<i32>,
b: Option<String>, // stays as-is
pub c: Option<Vec<u8>>,
}
// merge two partials (right-hand side only fills in missing values)
impl FooPartial {
// generated by the macro
pub fn merge(self, other: FooPartial) -> Self { ... }
}
// fallible conversion back to the original type
// (succeeds only if all non-optional original fields are present)
#[derive(thiserror::Error, Debug)]
pub enum FooPartialError {
#[error("a field is missing")]
AMissing,
#[error("c field is missing")]
CMissing,
}
impl TryFrom<FooPartial> for Foo {
type Error = FooPartialError;
fn try_from(partial: FooPartial) -> Result<Self, Self::Error> {
Ok(Foo {
a: partial.a.ok_or(FooPartialError::AMissing)?,
b: partial.b, // was already Option<_> in Foo
c: partial.c.ok_or(FooPartialError::CMissing)?,
})
}
}
merge method on *Partial typesFor every generated <OriginalName>Partial the macro adds a merge method:
impl FooPartial {
pub fn merge(self, other: FooPartial) -> Self {
Self {
a: self.a.or(other.a),
b: self.b.or(other.b),
c: self.c.or(other.c),
}
}
}
Semantics:
self is used if it is Some.self.field is None, the value from other.field is used.This is useful for layering configurations or patches:
let base: FooPartial = /* ... */;
let override_: FooPartial = /* ... */;
let merged = base.merge(override_);
TryFrom<*Partial> for OriginalFor each original struct Foo, the macro generates:
A dedicated error type: <OriginalName>PartialError (e.g. FooPartialError).
An implementation of TryFrom<FooPartial> for Foo:
Option in Foo
is Some in FooPartial.Option<...> in Foo are allowed to be None
without causing an error.enum annotated with thiserror::Error, with one
variant per non-optional field in the original struct.PascalCase using the convert_case crate.Missing is appended.a → AMissinguser_id → UserIdMissingThe error variants have messages of the form: "<field_name> field is missing".
Concrete example:
#[derive(optifier::Partial)]
pub struct User {
id: i64,
user_name: String,
email: Option<String>,
}
Generates something equivalent to:
pub struct UserPartial {
id: Option<i64>,
user_name: Option<String>,
email: Option<String>,
}
#[derive(thiserror::Error, Debug)]
pub enum UserPartialError {
#[error("id field is missing")]
IdMissing,
#[error("user_name field is missing")]
UserNameMissing,
// no variant for `email` (it was already Option<_> on User)
}
impl TryFrom<UserPartial> for User {
type Error = UserPartialError;
fn try_from(partial: UserPartial) -> Result<Self, Self::Error> {
Ok(User {
id: partial.id.ok_or(UserPartialError::IdMissing)?,
user_name: partial.user_name.ok_or(UserPartialError::UserNameMissing)?,
email: partial.email, // stays Option<_>
})
}
}
You can then use the standard try_into API:
let partial: UserPartial = /* ... */;
let user: User = partial.try_into()?; // or User::try_from(partial)?
#[optifier::partial_derive(...)] attributeThe #[optifier::partial_derive(...)] attribute configures which traits are derived
for the generated *Partial type.
#[derive(optifier::Partial)].Debug, Clone, PartialEq,
or fully qualified paths).#[derive(...)] on the generated
<OriginalName>Partial type.Example:
use optifier;
#[derive(optifier::Partial)]
#[optifier::partial_derive(Debug, Clone, PartialEq)]
pub struct Foo {
a: i32,
b: Option<String>,
}
generates roughly:
#[derive(Debug, Clone, PartialEq)]
pub struct FooPartial {
a: Option<i32>,
b: Option<String>,
}