Expand description
Library to derive optioned structs/enums versions of existing types
where all fields have been recursively replaced with versions that support setting just a subset of the relevant fields (or none at all).
One motivation for this concept is the common problem when expressing patches e.g. for Kubernetes apply configurations
that for a given rust struct T a corresponding struct T::Optioned would be required where all fields are recursively optional
to specify.
While trivial to write for plain structures this quickly becomes tedious for nested structs/enums.
§Deriving optional structs/enums
The core utility of this library is to provide an Optionable-derive macro that derives such an optioned type
and implements the corresponding Optionable-trait. It supports nested structures, enums as well as various container types.
For detailed configuration options via helper attributes, see the Optionable-derive macro docs.
The general logic is the same as for other rust derives, If you want to use the Optionable-derive macro for a struct/enum
every field of it needs to also have implemented the corresponding Optionable trait:
#[derive(Optionable)]
#[optionable(derive(Default,Serialize,Deserialize))]
struct Address {
street_name: String,
number: u8,
}
let _ = AddressOpt{
street_name: Some("a".to_owned()),
// fill the other fields with `None`
..Default::default()
};The generated optioned types are (shown here with resolved associated types) as follows. They can be also referenced as
Address::Optioned and AddressEnum::Optioned respectively.
#[derive(Default,Serialize,Deserialize)]
struct AddressOpt {
street_name: Option<String>,
number: Option<u8>,
}§Enum support
Deriving optioned versions also works with enums:
#[derive(Optionable)]
enum AddressEnum {
Plain(String),
AddressExplicit { street: String, number: u32 },
AddressNested(Address)
}
fn example(){
let _ = AddressEnumOpt::AddressExplicit{
street: Some("a".to_owned()),
number: None
};
}§Core concept
The main Optionable trait is quite simple:
pub trait Optionable {
type Optioned;
}It is a marker trait that allows to express for a given type T which type should be considered its T::Optioned type
such that Option<T::Optioned> would represent all variants of partial completeness.
For types without inner structure this means that the Optioned type will just resolve to the type itself, e.g.
impl Optionable for String {
type Optioned = String;
}For many primitive types as well as common wrapper or collection types the Optionable-trait is already implemented.
§Conversion
Per default also conversion traits for struct/enums with sized fields will be derived.
The relevant traits are OptionableConvert which is an extension trait for sized-fields only Optionable
objects. From this trait the sealed convenience trait OptionedConvert is auto-implemented
for the optioned object.
They are (shown here without comments and where clauses):
pub trait OptionableConvert: Sized + Optionable {
fn into_optioned(self) -> Self::Optioned;
fn try_from_optioned(value: Self::Optioned) -> Result<Self, Error>;
fn merge(&mut self, other: Self::Optioned) -> Result<(), Error>;
}
// sealed, auto-implemented from `OptionableConvert` for every respective `T::Optioned`
pub trait OptionedConvert<T>
where
T: Optionable<Optioned=Self> + OptionableConvert,
{
fn from_optionable(value: T) -> Self;
fn try_into_optionable(self) -> Result<T, Error>;
}§Crate features
derive: Default-feature, re-exports theOptionablederive macro.std: Default-feature. AddsOptionable-implementations for many stdlib types.alloc: AddsOptionable-implementations foralloctypes (only useful when not enabling thestdfeature).chrono: DeriveOptionablefor types from chrono.serde_json: DeriveOptionablefor serde_json::Value.
§Limitations
§External types
Due to the orphan rule the usage of the library becomes cumbersome if one has a use case which heavily relies on crate-external types.
For well-established libraries adding corresponding impl to this crate (feature-gated) would be a worthwhile approach.
§Resolving associated types
Due to the use of associated types some IDE-hints do not fully resolve the associated types leaving you with
<i32 as Optionable>::Optioned instead of i32. Luckily, for checking type correctness and also for error messages
when using wrong types the associated types are resolved.
For the derived Optioned-structs/enums a related issue is that other derive macros for those derived types won’t see the resolved
associated types. Therefore corresponding type bounds have to be added (done by the Optionable-derive) to the Optioned-structs/enums:
#[derive(Optionable)]
#[optionable(derive(Serialize))]
struct DeriveExample<T> {
name: T,
}
// The generated code for the struct is shown below (name adjusted and simplified)
#[derive(Serialize)]
struct DeriveExampleOpt2<T>
where
T: Optionable,
// extra `Serialize` bound on the struct level
<T as Optionable>::Optioned: Sized + Serialize,
{
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<<T as Optionable>::Optioned>
}§Similar crates
One crate with similar scope is optional_struct. It focuses specifically on structs (not enums) and offers a more manual approach, especially in respect to nested sub-struct, providing many fine-grained configuration options.
Another crate is struct-patch. It focuses on patching structs (not enums), especially from serde inputs. Nesting is supported with manual helper annotations.
Modules§
Traits§
- Optionable
- Marker trait that associated this type with a corresponding type where potential inner sub-fields are recursively optional if possible for the given use case of the type. Implementations of the trait can decide that some fields are also non-optional for the optioned type.
- Optionable
Convert - Extension trait for sized
Optionableto transform in and from optioned objects as well as merging. - Optioned
Convert - Sealed helper trait to transform from the perspective of the optioned type.
Will be automatically implemented for every target
<T as Optionable>:Optioned.
Derive Macros§
- Optionable
derive - Derive macro to derive the
Optionabletrait for structs/enums recursively by generating a type with all fields recursively replaced withOptionversions. All non-required fields have to implement theOptionabletrait. This trait is already implemented by this library for many primitive types, wrapper and container types.