forked from proxmox-mirrors/proxmox
updaters: docs and exports
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
cc065c175d
commit
034dd3fe5e
@ -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();
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user