From ab06665649e3c879d54a36a38bf5e6e565fac425 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Tue, 2 Feb 2021 14:05:19 +0100 Subject: [PATCH] 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>`, and all `Option` where `T: Updatable` are implicitly also updatable from `Option`. With the update to the `#[api]` macro, all `#[api]` types will implicitly `#[derive(Updatable)]` via the default implementation (which simply assignes from an `Option`), 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 --- proxmox/src/api/schema.rs | 84 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/proxmox/src/api/schema.rs b/proxmox/src/api/schema.rs index dea83361..257265ad 100644 --- a/proxmox/src/api/schema.rs +++ b/proxmox/src/api/schema.rs @@ -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` types. + /// Types which are not wrapped in `Option` must set this to `false`. + const UPDATER_IS_OPTION: bool; + + fn update_from(&mut self, from: Self::Updater, delete: &[T]) -> Result<(), Error> + where + T: AsRef; + fn try_build_from(from: Self::Updater) -> Result; +} + +macro_rules! basic_updatable { + ($($ty:ty)*) => { + $( + impl Updatable for $ty { + type Updater = Option<$ty>; + const UPDATER_IS_OPTION: bool = true; + + fn update_from>( + &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 { + from.ok_or_else(|| format_err!("cannot build from None value")) + } + } + )* + }; +} +basic_updatable! { bool u8 u16 u32 u64 i8 i16 i32 i64 String } + +impl Updatable for Option +where + T: Updatable, +{ + type Updater = T::Updater; + const UPDATER_IS_OPTION: bool = true; + + fn update_from>(&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 { + 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 Updater for Vec { + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +impl Updater for Option { + fn is_empty(&self) -> bool { + self.is_none() + } +}