updaters: docs and exports

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2021-02-02 14:28:32 +01:00
parent cc065c175d
commit 034dd3fe5e
2 changed files with 101 additions and 2 deletions

View File

@ -229,6 +229,87 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
declarations. If it contains a `schema` key, this is expected to be the path to an existing
schema. (Hence `type: Foo` is the same as `schema: Foo::API_SCHEMA`.)
# Deriving an `Updater`:
An "Updater" struct can be generated automatically for a type. This affects the `Updatable`
trait implementation generated, as it will set the associated
`type Updater = TheDerivedUpdater`.
In order to do this, simply add `#[derive(Updater)]` to the `#[api]`-macro using api type.
This is only supported for `struct`s with named fields and will generate a new `struct` whose
name is suffixed with `Updater` containing the `Updater` types of each field as a member.
Additionally the `#[updater(fixed)]` option is available to make it illegal for an updater to
modify a field (generating an error if it is set), while still allowing it to be used to create
a new object via the `build_from()` method.
```ignore
#[api]
/// An example of a simple struct type.
#[derive(Updater)]
pub struct MyType {
/// A string.
one: String,
/// An optional string.
/// Note that using `Option::is_empty` for the serde attribute only works for types which
/// use an `Option` as their `Updater`. For a `String` this works. Otherwise we'd have to
/// use `Updater::is_empty` instead.
#[serde(skip_serializing_if = "Option::is_none")]
opt: Option<String>,
}
```
The above will automatically generate the following:
```ignore
#[api]
/// An example of a simple struct type.
pub struct MyTypeUpdater {
one: Option<String>, // really <String as Updatable>::Updater
#[serde(skip_serializing_if = "Option::is_none")]
opt: Option<String>, // really <Option<String> as Updatable>::Updater
}
impl Updater for MyTypeUpdater {
fn is_empty(&self) -> bool {
self.one.is_empty() && self.opt.is_empty()
}
}
impl Updatable for MyType {
type Updater = MyTypeUpdater;
fn update_from<T>(&mut self, from: MyTypeUpdater, delete: &[T]) -> Result<(), Error>
where
T: AsRef<str>,
{
for delete in delete {
match delete.as_ref() {
"opt" => { self.opt = None; }
_ => (),
}
}
self.one.update_from(from.one)?;
self.opt.update_from(from.opt)?;
Ok(())
}
fn try_build_from(from: MyTypeUpdater) -> Result<Self, Error> {
Ok(Self {
// This amounts to `from.one.ok_or_else("cannot build from None value")?`
one: Updatable::try_build_from(from.one)
.map_err(|err| format_err!("failed to build value for field 'one': {}", err))?,
// This amounts to `from.opt`
opt: Updatable::try_build_from(from.opt)
.map_err(|err| format_err!("failed to build value for field 'opt': {}", err))?,
})
}
}
```
*/
#[proc_macro_attribute]
pub fn api(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 {
@ -238,12 +319,13 @@ pub fn api(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 {
}
/// This is a dummy derive macro actually handled by `#[api]`!
#[doc(hidden)]
#[proc_macro_derive(Updater, attributes(updater, updatable, serde))]
pub fn derive_updater(_item: TokenStream_1) -> TokenStream_1 {
TokenStream_1::new()
}
/// Create the default `Updatable` implementation from an `Option`.
/// Create the default `Updatable` implementation from an `Option<Self>`.
#[proc_macro_derive(Updatable, attributes(updatable, serde))]
pub fn derive_updatable(item: TokenStream_1) -> TokenStream_1 {
let _error_guard = init_local_error();

View File

@ -1507,6 +1507,9 @@ fn test_verify_complex_array() {
}
/// API types are "updatable" in order to support derived "Updater" structs more easily.
///
/// By default, any API type is "updatable" by an `Option<Self>`. For types which do not use the
/// `#[api]` macro, this will need to be explicitly created (or derived via `#[derive(Updatable)]`.
pub trait Updatable: Sized {
type Updater: Updater;
/// This should always be true for the "default" updaters which are just `Option<T>` types.
@ -1519,6 +1522,13 @@ pub trait Updatable: Sized {
fn try_build_from(from: Self::Updater) -> Result<Self, Error>;
}
#[cfg(feature = "api-macro")]
pub use proxmox_api_macro::Updatable;
#[cfg(feature = "api-macro")]
#[doc(hidden)]
pub use proxmox_api_macro::Updater;
macro_rules! basic_updatable {
($($ty:ty)*) => {
$(
@ -1572,7 +1582,14 @@ where
}
}
/// A helper type for "Updater" structs.
/// A helper type for "Updater" structs. This trait is *not* implemented for an api "base" type
/// when deriving an `Updater` for it, though the generated *updater* type does implement this
/// trait!
///
/// This trait is mostly to figure out if an updater is empty (iow. it should not be applied).
/// In that, it is useful when a type which should have an updater also has optional fields which
/// should not be serialized. Instead of `#[serde(skip_serializing_if = "Option::is_none")]`, this
/// trait's `is_empty` needs to be used via `#[serde(skip_serializing_if = "Updater::is_empty")]`.
pub trait Updater {
/// Check if the updater is "none" or "empty".
fn is_empty(&self) -> bool;