schema: introduce Updatable and Updater traits

The `Updatable` trait is our mechanism for "updating"
values. The `Updater` trait is a serde-helper to allow using
`skip_serializing_if` on api type structs which
`#[derive(Updater)]`

All basic types implement `Updatable<Updater = Option<T>>`,
and all `Option<T>` where `T: Updatable` are implicitly also
updatable from `Option<T>`.

With the update to the `#[api]` macro, all `#[api]` types
will implicitly `#[derive(Updatable)]` via the default
implementation (which simply assignes from an
`Option<Self>`), unless they explicitly also
`#[derive(Updater)]` in which case an implementation is
derived which goes through each "field" found in the
updater. To prevent fields from being updatable via an
Updater `#[updater(fixed)]` can be used.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2021-02-02 14:05:19 +01:00
parent 237f206235
commit ab06665649

View File

@ -1505,3 +1505,87 @@ fn test_verify_complex_array() {
assert!(res.is_err());
}
}
/// API types are "updatable" in order to support derived "Updater" structs more easily.
pub trait Updatable: Sized {
type Updater: Updater;
/// This should always be true for the "default" updaters which are just `Option<T>` types.
/// Types which are not wrapped in `Option` must set this to `false`.
const UPDATER_IS_OPTION: bool;
fn update_from<T>(&mut self, from: Self::Updater, delete: &[T]) -> Result<(), Error>
where
T: AsRef<str>;
fn try_build_from(from: Self::Updater) -> Result<Self, Error>;
}
macro_rules! basic_updatable {
($($ty:ty)*) => {
$(
impl Updatable for $ty {
type Updater = Option<$ty>;
const UPDATER_IS_OPTION: bool = true;
fn update_from<T: AsRef<str>>(
&mut self,
from: Option<$ty>,
_delete: &[T],
) -> Result<(), Error> {
if let Some(val) = from {
*self = val;
}
Ok(())
}
fn try_build_from(from: Option<$ty>) -> Result<Self, Error> {
from.ok_or_else(|| format_err!("cannot build from None value"))
}
}
)*
};
}
basic_updatable! { bool u8 u16 u32 u64 i8 i16 i32 i64 String }
impl<T> Updatable for Option<T>
where
T: Updatable,
{
type Updater = T::Updater;
const UPDATER_IS_OPTION: bool = true;
fn update_from<S: AsRef<str>>(&mut self, from: T::Updater, delete: &[S]) -> Result<(), Error> {
match self {
Some(val) => val.update_from(from, delete),
None => {
*self = Self::try_build_from(from)?;
Ok(())
}
}
}
fn try_build_from(from: T::Updater) -> Result<Self, Error> {
if from.is_empty() {
Ok(None)
} else {
T::try_build_from(from).map(Some)
}
}
}
/// A helper type for "Updater" structs.
pub trait Updater {
/// Check if the updater is "none" or "empty".
fn is_empty(&self) -> bool;
}
impl<T> Updater for Vec<T> {
fn is_empty(&self) -> bool {
self.is_empty()
}
}
impl<T> Updater for Option<T> {
fn is_empty(&self) -> bool {
self.is_none()
}
}