Crate optionable

Crate optionable 

Source
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 the Optionable derive macro.
  • std: Default-feature. Adds Optionable-implementations for many stdlib types.
  • alloc: Adds Optionable-implementations for alloc types (only useful when not enabling the std feature).
  • chrono: Derive Optionable for types from chrono.
  • serde_json: Derive Optionable for 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§

optionable

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.
OptionableConvert
Extension trait for sized Optionable to transform in and from optioned objects as well as merging.
OptionedConvert
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§

Optionablederive
Derive macro to derive the Optionable trait for structs/enums recursively by generating a type with all fields recursively replaced with Option versions. All non-required fields have to implement the Optionable trait. This trait is already implemented by this library for many primitive types, wrapper and container types.