# Optifier
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:
- an inherent `merge` method to combine two partial values, and
- a `TryFrom<FooPartial> for Foo` implementation with a dedicated error type.
Example:
```rust
use optifier;
#[derive(optifier::Partial)]
#[optifier::partial_derive(Debug, Clone)]
pub struct Foo {
a: i32,
b: Option<String>,
pub c: Vec<u8>,
}
```
expands to:
```rust
#[derive(Debug, Clone)]
pub struct FooPartial {
a: Option<i32>,
b: Option<String>, pub c: Option<Vec<u8>>,
}
impl FooPartial {
pub fn merge(self, other: FooPartial) -> Self { ... }
}
#[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, c: partial.c.ok_or(FooPartialError::CMissing)?,
})
}
}
```
## `merge` method on `*Partial` types
For every generated `<OriginalName>Partial` the macro adds a `merge` method:
```rust
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:
- It returns a new partial where, for each field, the value from `self` is used if it is `Some`.
- If `self.field` is `None`, the value from `other.field` is used.
- This is a shallow merge; it does not recurse into nested structs.
This is useful for layering configurations or patches:
```rust
let base: FooPartial = /* ... */;
let override_: FooPartial = /* ... */;
let merged = base.merge(override_);
```
## Fallible conversion: `TryFrom<*Partial> for Original`
For each original struct `Foo`, the macro generates:
- A dedicated error type: `<OriginalName>PartialError` (e.g. `FooPartialError`).
- An implementation of `TryFrom<FooPartial> for Foo`:
- The conversion succeeds only if every field that was non-`Option` in `Foo`
is `Some` in `FooPartial`.
- Fields that were already `Option<...>` in `Foo` are allowed to be `None`
without causing an error.
- The error type is an `enum` annotated with `thiserror::Error`, with one
variant per non-optional field in the original struct.
- Variant naming:
- Field name is converted to `PascalCase` using the `convert_case` crate.
- The suffix `Missing` is appended.
- Examples:
- `a` → `AMissing`
- `user_id` → `UserIdMissing`
The error variants have messages of the form: `"<field_name> field is missing"`.
Concrete example:
```rust
#[derive(optifier::Partial)]
pub struct User {
id: i64,
user_name: String,
email: Option<String>,
}
```
Generates something equivalent to:
```rust
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:
```rust
let partial: UserPartial = /* ... */;
let user: User = partial.try_into()?; // or User::try_from(partial)?
```
## `#[optifier::partial_derive(...)]` attribute
The `#[optifier::partial_derive(...)]` attribute configures which traits are derived
for the generated `*Partial` type.
- It is applied to the original struct, together with `#[derive(optifier::Partial)]`.
- It accepts a comma-separated list of trait paths (e.g. `Debug`, `Clone`, `PartialEq`,
or fully qualified paths).
- Whatever traits you list there are emitted as a `#[derive(...)]` on the generated
`<OriginalName>Partial` type.
Example:
```rust
use optifier;
#[derive(optifier::Partial)]
#[optifier::partial_derive(Debug, Clone, PartialEq)]
pub struct Foo {
a: i32,
b: Option<String>,
}
```
generates roughly:
```rust
#[derive(Debug, Clone, PartialEq)]
pub struct FooPartial {
a: Option<i32>,
b: Option<String>,
}
```