optifier 0.1.0-beta.3

Rust macros for deriving optional types
Documentation
# 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>, // 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` 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>,
}
```