]> git.proxmox.com Git - proxmox.git/commitdiff
change updater derivation
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 10 Aug 2021 09:41:41 +0000 (11:41 +0200)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 10 Aug 2021 10:03:29 +0000 (12:03 +0200)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
proxmox-api-macro/src/api/attributes.rs [new file with mode: 0644]
proxmox-api-macro/src/api/enums.rs
proxmox-api-macro/src/api/mod.rs
proxmox-api-macro/src/api/structs.rs
proxmox-api-macro/src/updater.rs
proxmox-api-macro/src/util.rs
proxmox-api-macro/tests/updater.rs
proxmox/src/api/schema.rs

diff --git a/proxmox-api-macro/src/api/attributes.rs b/proxmox-api-macro/src/api/attributes.rs
new file mode 100644 (file)
index 0000000..9670f8a
--- /dev/null
@@ -0,0 +1,52 @@
+use syn::{Meta, NestedMeta};
+
+use crate::util::{self, default_false, set_bool};
+
+#[derive(Default)]
+pub struct UpdaterFieldAttributes {
+    /// Skip this field in the updater.
+    skip: Option<syn::LitBool>,
+    // /// Change the type for the updater.
+    // ty: Option<syn::Type>,
+}
+
+impl UpdaterFieldAttributes {
+    pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> Self {
+        let mut this = Self::default();
+
+        util::extract_attributes(input, "updater", |meta| this.parse(meta));
+
+        this
+    }
+
+    fn parse(&mut self, input: NestedMeta) -> Result<(), syn::Error> {
+        match input {
+            NestedMeta::Lit(lit) => bail!(lit => "unexpected literal"),
+            NestedMeta::Meta(meta) => self.parse_meta(meta),
+        }
+    }
+
+    fn parse_meta(&mut self, meta: Meta) -> Result<(), syn::Error> {
+        match meta {
+            Meta::Path(ref path) if path.is_ident("skip") => {
+                set_bool(&mut self.skip, path, true);
+            }
+            // Meta::NameValue(ref nv) if nv.path.is_ident("type") => {
+            //     parse_str_value_to_option(&mut self.ty, nv)
+            // }
+            Meta::NameValue(m) => bail!(&m => "invalid updater attribute: {:?}", m.path),
+            Meta::List(m) => bail!(&m => "invalid updater attribute: {:?}", m.path),
+            Meta::Path(m) => bail!(&m => "invalid updater attribute: {:?}", m),
+        }
+
+        Ok(())
+    }
+
+    pub fn skip(&self) -> bool {
+        default_false(self.skip.as_ref())
+    }
+
+    //pub fn ty(&self) -> Option<&syn::Type> {
+    //    self.ty.as_ref()
+    //}
+}
index 105d3043fcb16c3a2529889a89b0093e8c3df65e..4654e0e26a28ae4031387fe23a8d9f07becef6eb 100644 (file)
@@ -82,5 +82,11 @@ pub fn handle_enum(
                 .format(&::proxmox::api::schema::ApiStringFormat::Enum(&[#variants]))
                 .schema();
         }
+
+        #[automatically_derived]
+        impl ::proxmox::api::schema::Updatable for #name {
+            type Updater = Option<Self>;
+            const UPDATER_IS_OPTION: bool = true;
+        }
     })
 }
index 5c7d912db475df82a1d7db534fb99b2bc33227dc..60c3ce7a19702a048f232e1ba5b64145bc43879e 100644 (file)
@@ -19,6 +19,7 @@ use syn::{Expr, ExprPath, Ident};
 
 use crate::util::{FieldName, JSONObject, JSONValue, Maybe};
 
+mod attributes;
 mod enums;
 mod method;
 mod structs;
@@ -198,6 +199,12 @@ impl Schema {
             .and_then(|obj| obj.find_property_by_ident_mut(key))
     }
 
+    fn remove_obj_property_by_ident(&mut self, key: &str) -> bool {
+        self.as_object_mut()
+            .expect("tried to remove object property on non-object schema")
+            .remove_property_by_ident(key)
+    }
+
     // FIXME: Should we turn the property list into a map? We used to have no need to find keys in
     // it, but we do now...
     fn find_schema_property(&self, key: &str) -> Option<&syn::Expr> {
@@ -644,6 +651,20 @@ impl SchemaObject {
             .find(|p| p.name.as_ident_str() == key)
     }
 
+    fn remove_property_by_ident(&mut self, key: &str) -> bool {
+        match self
+            .properties_
+            .iter()
+            .position(|p| p.name.as_ident_str() == key)
+        {
+            Some(idx) => {
+                self.properties_.remove(idx);
+                true
+            }
+            None => false,
+        }
+    }
+
     fn extend_properties(&mut self, new_fields: Vec<ObjectEntry>) {
         self.properties_.extend(new_fields);
         self.sort_properties();
index 806c5284c82ef55a60ea7f7c3e53f3c15bebf312..d7973518d8fd6f0d0089e290143e7ed2bc023390 100644 (file)
@@ -18,6 +18,7 @@ use anyhow::Error;
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::quote_spanned;
 
+use super::attributes::UpdaterFieldAttributes;
 use super::Schema;
 use crate::api::{self, ObjectEntry, SchemaItem};
 use crate::serde;
@@ -58,7 +59,16 @@ fn handle_unit_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<Toke
 
     get_struct_description(&mut schema, &stru)?;
 
-    finish_schema(schema, &stru, &stru.ident)
+    let name = &stru.ident;
+    let mut schema = finish_schema(schema, &stru, name)?;
+    schema.extend(quote_spanned! { name.span() =>
+        impl ::proxmox::api::schema::Updatable for #name {
+            type Updater = Option<Self>;
+            const UPDATER_IS_OPTION: bool = true;
+        }
+    });
+
+    Ok(schema)
 }
 
 fn finish_schema(
@@ -261,7 +271,16 @@ fn handle_regular_struct(
             }
         });
         if derive {
-            derive_updater(stru.clone(), schema.clone(), &mut stru)?
+            let updater = derive_updater(stru.clone(), schema.clone(), &mut stru)?;
+
+            // make sure we don't leave #[updater] attributes on the original struct:
+            if let syn::Fields::Named(fields) = &mut stru.fields {
+                for field in &mut fields.named {
+                    let _ = UpdaterFieldAttributes::from_attributes(&mut field.attrs);
+                }
+            }
+
+            updater
         } else {
             TokenStream::new()
         }
@@ -381,7 +400,7 @@ fn derive_updater(
     stru.ident = Ident::new(&format!("{}Updater", stru.ident), stru.ident.span());
 
     if !util::derived_items(&original_struct.attrs).any(|p| p.is_ident("Default")) {
-        original_struct.attrs.push(util::make_derive_attribute(
+        stru.attrs.push(util::make_derive_attribute(
             Span::call_site(),
             quote::quote! { Default },
         ));
@@ -404,51 +423,20 @@ fn derive_updater(
     let mut is_empty_impl = TokenStream::new();
 
     if let syn::Fields::Named(fields) = &mut stru.fields {
-        for field in &mut fields.named {
-            let field_name = field.ident.as_ref().expect("unnamed field in FieldsNamed");
-            let field_name_string = field_name.to_string();
-
-            let field_schema = match schema.find_obj_property_by_ident_mut(&field_name_string) {
-                Some(obj) => obj,
-                None => {
-                    error!(
-                        field_name.span(),
-                        "failed to find schema entry for {:?}", field_name_string,
-                    );
-                    continue;
+        for mut field in std::mem::take(&mut fields.named) {
+            match handle_updater_field(
+                &mut field,
+                &mut schema,
+                &mut all_of_schemas,
+                &mut is_empty_impl,
+            ) {
+                Ok(FieldAction::Keep) => fields.named.push(field),
+                Ok(FieldAction::Skip) => (),
+                Err(err) => {
+                    crate::add_error(err);
+                    fields.named.push(field);
                 }
-            };
-
-            field_schema.optional = field.ty.clone().into();
-
-            let span = Span::call_site();
-            let updater = syn::TypePath {
-                qself: Some(syn::QSelf {
-                    lt_token: syn::token::Lt { spans: [span] },
-                    ty: Box::new(field.ty.clone()),
-                    position: 4, // 'Updater' is the 4th item in the 'segments' below
-                    as_token: Some(syn::token::As { span }),
-                    gt_token: syn::token::Gt { spans: [span] },
-                }),
-                path: util::make_path(
-                    span,
-                    true,
-                    &["proxmox", "api", "schema", "Updatable", "Updater"],
-                ),
-            };
-            field.ty = syn::Type::Path(updater);
-
-            if field_schema.flatten_in_struct {
-                let updater_ty = &field.ty;
-                all_of_schemas.extend(quote::quote! {&#updater_ty::API_SCHEMA,});
-            }
-
-            if !is_empty_impl.is_empty() {
-                is_empty_impl.extend(quote::quote! { && });
             }
-            is_empty_impl.extend(quote::quote! {
-                self.#field_name.is_empty()
-            });
         }
     }
 
@@ -471,3 +459,75 @@ fn derive_updater(
 
     Ok(output)
 }
+
+enum FieldAction {
+    Keep,
+    Skip,
+}
+
+fn handle_updater_field(
+    field: &mut syn::Field,
+    schema: &mut Schema,
+    all_of_schemas: &mut TokenStream,
+    is_empty_impl: &mut TokenStream,
+) -> Result<FieldAction, syn::Error> {
+    let updater_attrs = UpdaterFieldAttributes::from_attributes(&mut field.attrs);
+
+    let field_name = field.ident.as_ref().expect("unnamed field in FieldsNamed");
+    let field_name_string = field_name.to_string();
+
+    if updater_attrs.skip() {
+        if !schema.remove_obj_property_by_ident(&field_name_string) {
+            bail!(
+                field_name.span(),
+                "failed to find schema entry for {:?}",
+                field_name_string,
+            );
+        }
+        return Ok(FieldAction::Skip);
+    }
+
+    let field_schema = match schema.find_obj_property_by_ident_mut(&field_name_string) {
+        Some(obj) => obj,
+        None => {
+            bail!(
+                field_name.span(),
+                "failed to find schema entry for {:?}",
+                field_name_string,
+            );
+        }
+    };
+
+    field_schema.optional = field.ty.clone().into();
+
+    let span = Span::call_site();
+    let updater = syn::TypePath {
+        qself: Some(syn::QSelf {
+            lt_token: syn::token::Lt { spans: [span] },
+            ty: Box::new(field.ty.clone()),
+            position: 4, // 'Updater' is the 4th item in the 'segments' below
+            as_token: Some(syn::token::As { span }),
+            gt_token: syn::token::Gt { spans: [span] },
+        }),
+        path: util::make_path(
+            span,
+            true,
+            &["proxmox", "api", "schema", "Updatable", "Updater"],
+        ),
+    };
+    field.ty = syn::Type::Path(updater);
+
+    if field_schema.flatten_in_struct {
+        let updater_ty = &field.ty;
+        all_of_schemas.extend(quote::quote! {&#updater_ty::API_SCHEMA,});
+    }
+
+    if !is_empty_impl.is_empty() {
+        is_empty_impl.extend(quote::quote! { && });
+    }
+    is_empty_impl.extend(quote::quote! {
+        self.#field_name.is_empty()
+    });
+
+    Ok(FieldAction::Keep)
+}
index 2d037bd6250555e859864b04fffe27dae5b4ad05..b596f13c7a7532f2655256cb34deeb022e674908 100644 (file)
@@ -1,12 +1,7 @@
-use std::convert::TryFrom;
-
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{quote, quote_spanned};
 use syn::spanned::Spanned;
 
-use crate::serde;
-use crate::util;
-
 pub(crate) fn updatable(item: TokenStream) -> Result<TokenStream, syn::Error> {
     let item: syn::Item = syn::parse2(item)?;
     let full_span = item.span();
@@ -64,22 +59,6 @@ fn default_updatable(full_span: Span, ident: Ident) -> TokenStream {
         impl ::proxmox::api::schema::Updatable for #ident {
             type Updater = Option<#ident>;
             const UPDATER_IS_OPTION: bool = true;
-
-            fn update_from<T: AsRef<str>>(
-                &mut self,
-                from: Option<#ident>,
-                _delete: &[T],
-            ) -> Result<(), ::anyhow::Error> {
-                if let Some(val) = from {
-                    *self = val;
-                }
-
-                Ok(())
-            }
-
-            fn try_build_from(from: Option<#ident>) -> Result<Self, ::anyhow::Error> {
-                from.ok_or_else(|| ::anyhow::format_err!("cannot build from None value"))
-            }
         }
     }
 }
@@ -89,115 +68,21 @@ fn derive_named_struct_updatable(
     full_span: Span,
     ident: Ident,
     generics: syn::Generics,
-    fields: syn::FieldsNamed,
+    _fields: syn::FieldsNamed,
 ) -> Result<TokenStream, syn::Error> {
     no_generics(generics);
 
-    let serde_container_attrs = serde::ContainerAttrib::try_from(&attrs[..])?;
     let args = UpdatableArgs::from_attributes(attrs);
     let updater = match args.updater {
         Some(updater) => updater,
         None => return Ok(default_updatable(full_span, ident)),
     };
 
-    let mut delete = TokenStream::new();
-    let mut apply = TokenStream::new();
-    let mut build = TokenStream::new();
-
-    for field in fields.named {
-        let serde_attrs = serde::SerdeAttrib::try_from(&field.attrs[..])?;
-        let attrs = UpdaterFieldArgs::from_attributes(field.attrs);
-
-        let field_ident = field
-            .ident
-            .as_ref()
-            .expect("unnamed field in named struct?");
-
-        let field_name_string = if let Some(renamed) = serde_attrs.rename {
-            renamed.into_str()
-        } else if let Some(rename_all) = serde_container_attrs.rename_all {
-            let name = rename_all.apply_to_field(&field_ident.to_string());
-            name
-        } else {
-            field_ident.to_string()
-        };
-
-        let build_err = format!(
-            "failed to build value for field '{}': {{}}",
-            field_name_string
-        );
-        if util::is_option_type(&field.ty).is_some() {
-            delete.extend(quote! {
-                #field_name_string => { self.#field_ident = None; }
-            });
-            build.extend(quote! {
-                #field_ident: ::proxmox::api::schema::Updatable::try_build_from(
-                    from.#field_ident
-                )
-                .map_err(|err| ::anyhow::format_err!(#build_err, err))?,
-            });
-        } else {
-            build.extend(quote! {
-                #field_ident: ::proxmox::api::schema::Updatable::try_build_from(
-                    from.#field_ident
-                )
-                .map_err(|err| ::anyhow::format_err!(#build_err, err))?,
-            });
-        }
-
-        if attrs.fixed {
-            let error = format!(
-                "field '{}' must not be set when updating existing data",
-                field_ident
-            );
-            apply.extend(quote! {
-                if from.#field_ident.is_some() {
-                    ::anyhow::bail!(#error);
-                }
-            });
-        } else {
-            apply.extend(quote! {
-                ::proxmox::api::schema::Updatable::update_from(
-                    &mut self.#field_ident,
-                    from.#field_ident,
-                    delete,
-                )?;
-            });
-        }
-    }
-
-    if !delete.is_empty() {
-        delete = quote! {
-            for delete in delete {
-                match delete.as_ref() {
-                    #delete
-                    _ => continue,
-                }
-            }
-        };
-    }
-
     Ok(quote! {
         #[automatically_derived]
         impl ::proxmox::api::schema::Updatable for #ident {
             type Updater = #updater;
             const UPDATER_IS_OPTION: bool = false;
-
-            fn update_from<T: AsRef<str>>(
-                &mut self,
-                from: Self::Updater,
-                delete: &[T],
-            ) -> Result<(), ::anyhow::Error> {
-                #delete
-                #apply
-                Ok(())
-            }
-
-            fn try_build_from(from: Self::Updater) -> Result<Self, ::anyhow::Error> {
-                Ok(Self {
-                    #build
-                })
-            }
         }
     })
 }
@@ -244,31 +129,6 @@ impl UpdatableArgs {
     }
 }
 
-#[derive(Default)]
-struct UpdaterFieldArgs {
-    /// A fixed field must not be set in the `Updater` when the data is updated via `update_from`,
-    /// but is still required for the `build()` method.
-    fixed: bool,
-}
-
-impl UpdaterFieldArgs {
-    fn from_attributes(attributes: Vec<syn::Attribute>) -> Self {
-        let mut this = Self::default();
-        for_attributes(attributes, "updater", |meta| this.parse_nested(meta));
-        this
-    }
-
-    fn parse_nested(&mut self, meta: syn::NestedMeta) -> Result<(), syn::Error> {
-        match meta {
-            syn::NestedMeta::Meta(syn::Meta::Path(path)) if path.is_ident("fixed") => {
-                self.fixed = true;
-            }
-            other => bail!(other => "invalid updater argument"),
-        }
-        Ok(())
-    }
-}
-
 /// Non-fatally go through all `updater` attributes.
 fn for_attributes<F>(attributes: Vec<syn::Attribute>, attr_name: &str, mut func: F)
 where
index 87d29a5ef142f3dadda23a123c7511923bb2fae8..77e0098df4f0d4f287ffc2a7d3cead3fd2c28530 100644 (file)
@@ -376,7 +376,7 @@ impl IntoIterator for JSONObject {
 /// An element in a json style map.
 struct JSONMapEntry {
     pub key: FieldName,
-    pub colon_token: Token![:],
+    _colon_token: Token![:],
     pub value: JSONValue,
 }
 
@@ -384,7 +384,7 @@ impl Parse for JSONMapEntry {
     fn parse(input: ParseStream) -> syn::Result<Self> {
         Ok(Self {
             key: input.parse()?,
-            colon_token: input.parse()?,
+            _colon_token: input.parse()?,
             value: input.parse()?,
         })
     }
@@ -816,3 +816,116 @@ pub fn make_derive_attribute(span: Span, content: TokenStream) -> syn::Attribute
         quote::quote! { (#content) },
     )
 }
+
+/// Extract (remove) an attribute from a list an run a callback on its parameters.
+pub fn extract_attributes(
+    attributes: &mut Vec<syn::Attribute>,
+    attr_name: &str,
+    mut func_matching: impl FnMut(syn::NestedMeta) -> Result<(), syn::Error>,
+) {
+    for attr in std::mem::take(attributes) {
+        if attr.style != syn::AttrStyle::Outer {
+            attributes.push(attr);
+            continue;
+        }
+
+        let meta = match attr.parse_meta() {
+            Ok(meta) => meta,
+            Err(err) => {
+                crate::add_error(err);
+                attributes.push(attr);
+                continue;
+            }
+        };
+
+        let list = match meta {
+            syn::Meta::List(list) if list.path.is_ident(attr_name) => list,
+            _ => {
+                attributes.push(attr);
+                continue;
+            }
+        };
+
+        for entry in list.nested {
+            match func_matching(entry) {
+                Ok(()) => (),
+                Err(err) => crate::add_error(err),
+            }
+        }
+    }
+}
+
+/// Helper to create an error about some duplicate attribute.
+pub fn duplicate<T>(prev: &Option<T>, attr: &syn::Path) {
+    if prev.is_some() {
+        error!(attr => "duplicate attribute: '{:?}'", attr)
+    }
+}
+
+/// Set a boolean attribute to a value, producing a "duplication" error if it has already been set.
+pub fn set_bool(b: &mut Option<syn::LitBool>, attr: &syn::Path, value: bool) {
+    duplicate(&*b, attr);
+    *b = Some(syn::LitBool::new(value, attr.span()));
+}
+
+pub fn default_false(o: Option<&syn::LitBool>) -> bool {
+    o.as_ref().map(|b| b.value).unwrap_or(false)
+}
+
+/*
+/// Parse the contents of a `LitStr`, preserving its span.
+pub fn parse_lit_str<T: Parse>(s: &syn::LitStr) -> syn::parse::Result<T> {
+    parse_str(&s.value(), s.span())
+}
+
+/// Parse a literal string, giving the entire output the specified span.
+pub fn parse_str<T: Parse>(s: &str, span: Span) -> syn::parse::Result<T> {
+    syn::parse2(respan_tokens(syn::parse_str(s)?, span))
+}
+
+/// Apply a `Span` to an entire `TokenStream`.
+pub fn respan_tokens(stream: TokenStream, span: Span) -> TokenStream {
+    stream
+        .into_iter()
+        .map(|token| respan(token, span))
+        .collect()
+}
+
+/// Apply a `Span` to a `TokenTree`, recursively if it is a `Group`.
+pub fn respan(mut token: TokenTree, span: Span) -> TokenTree {
+    use proc_macro2::Group;
+
+    match &mut token {
+        TokenTree::Group(g) => {
+            *g = Group::new(g.delimiter(), respan_tokens(g.stream(), span));
+        }
+        other => other.set_span(span),
+    }
+
+    token
+}
+
+/// Parse a string attribute into a value, producing a duplication error if it has already been
+/// set.
+pub fn parse_str_value_to_option<T: Parse>(target: &mut Option<T>, nv: &syn::MetaNameValue) {
+    duplicate(&*target, &nv.path);
+    match &nv.lit {
+        syn::Lit::Str(s) => match parse_lit_str(s) {
+            Ok(value) => *target = Some(value),
+            Err(err) => crate::add_error(err),
+        },
+        other => error!(other => "bad value for '{:?}' attribute", nv.path),
+    }
+}
+
+pub fn parse_str_value<T: Parse>(nv: &syn::MetaNameValue) -> Result<T, syn::Error> {
+    match &nv.lit {
+        syn::Lit::Str(s) => super::parse_lit_str(s),
+        other => bail!(other => "bad value for '{:?}' attribute", nv.path),
+    }
+}
+
+pub fn default_true(o: Option<&syn::LitBool>) -> bool {
+    o.as_ref().map(|b| b.value).unwrap_or(true)
+}
+*/
index 0d570ef7efcc6d5ba0fbc9262d4ee3a738ea78d2..02ade72851c5e48c03dc12843eedc25f7241029a 100644 (file)
@@ -1,14 +1,9 @@
-#[cfg(not(feature = "noserde"))]
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-
 use proxmox::api::api;
 use proxmox::api::schema::Updater;
 
 #[api]
 /// An example of a simple struct type.
-#[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
-#[derive(Debug, PartialEq, Updater)]
+#[derive(Updater)]
 #[serde(rename_all = "kebab-case")]
 pub struct Simple {
     /// A test string.
@@ -25,8 +20,7 @@ pub struct Simple {
     },
 )]
 /// A second struct so we can test flattening.
-#[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
-#[derive(Debug, PartialEq, Updater)]
+#[derive(Updater)]
 pub struct Complex {
     /// An extra field not part of the flattened struct.
     extra: String,
@@ -44,288 +38,16 @@ pub struct Complex {
     },
 )]
 /// One of the baaaad cases.
-#[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
-#[derive(Debug, PartialEq, Updater)]
+#[derive(Updater)]
+#[serde(rename_all = "kebab-case")]
 pub struct SuperComplex {
     /// An extra field not part of the flattened struct.
     extra: String,
 
     #[serde(skip_serializing_if = "Updater::is_empty")]
     simple: Option<Simple>,
-}
-
-#[api(
-    properties: {
-            complex: { type: Complex },
-    },
-)]
-/// Something with "fixed" values we cannot update but require for creation.
-#[cfg_attr(not(feature = "noserde"), derive(Deserialize, Serialize))]
-#[derive(Debug, PartialEq, Updater)]
-pub struct Creatable {
-    /// An ID which cannot be changed later.
-    #[updater(fixed)]
-    id: String,
-
-    /// Some parameter we're allowed to change with an updater.
-    name: String,
-
-    /// Optional additional information.
-    #[serde(skip_serializing_if = "Updater::is_empty", default)]
-    info: Option<String>,
-
-    /// Optional additional information 2.
-    #[serde(skip_serializing_if = "Updater::is_empty", default)]
-    info2: Option<String>,
-
-    /// Super complex additional data
-    #[serde(flatten)]
-    complex: Complex,
-}
-
-struct RpcEnv;
-impl proxmox::api::RpcEnvironment for RpcEnv {
-    fn result_attrib_mut(&mut self) -> &mut Value {
-        panic!("result_attrib_mut called");
-    }
-
-    fn result_attrib(&self) -> &Value {
-        panic!("result_attrib called");
-    }
-
-    /// The environment type
-    fn env_type(&self) -> proxmox::api::RpcEnvironmentType {
-        panic!("env_type called");
-    }
-
-    /// Set authentication id
-    fn set_auth_id(&mut self, user: Option<String>) {
-        let _ = user;
-        panic!("set_auth_id called");
-    }
-
-    /// Get authentication id
-    fn get_auth_id(&self) -> Option<String> {
-        panic!("get_auth_id called");
-    }
-}
-
-mod test_creatable {
-    use anyhow::{bail, Error};
-    use serde_json::json;
-
-    use proxmox::api::schema::Updatable;
-    use proxmox_api_macro::api;
-
-    use super::*;
-
-    static mut TEST_OBJECT: Option<Creatable> = None;
-
-    #[api(
-        input: {
-            properties: {
-                thing: { flatten: true, type: CreatableUpdater },
-            },
-        },
-    )]
-    /// Test method to create an object.
-    ///
-    /// Returns: the object's ID.
-    pub fn create_thing(thing: CreatableUpdater) -> Result<String, Error> {
-        if unsafe { TEST_OBJECT.is_some() } {
-            bail!("object exists");
-        }
-
-        let obj = Creatable::try_build_from(thing)?;
-        let id = obj.id.clone();
-
-        unsafe {
-            TEST_OBJECT = Some(obj);
-        }
-
-        Ok(id)
-    }
-
-    #[api(
-        input: {
-            properties: {
-                thing: { flatten: true, type: CreatableUpdater },
-                delete: {
-                    optional: true,
-                    description: "list of properties to delete",
-                    type: Array,
-                    items: {
-                        description: "field name to delete",
-                        type: String,
-                    },
-                },
-            },
-        },
-    )]
-    /// Test method to update an object.
-    pub fn update_thing(thing: CreatableUpdater, delete: Option<Vec<String>>) -> Result<(), Error> {
-        let delete = delete.unwrap_or_default();
-        match unsafe { &mut TEST_OBJECT } {
-            Some(obj) => obj.update_from(thing, &delete),
-            None => bail!("object has not been created yet"),
-        }
-    }
-
-    #[test]
-    fn test() {
-        let _ = api_function_create_thing(
-            json!({ "name": "The Name" }),
-            &API_METHOD_CREATE_THING,
-            &mut RpcEnv,
-        )
-        .expect_err("create_thing should fail without an ID");
-
-        let _ = api_function_create_thing(
-            json!({ "id": "Id1" }),
-            &API_METHOD_CREATE_THING,
-            &mut RpcEnv,
-        )
-        .expect_err("create_thing should fail without a name");
-
-        let value = api_function_create_thing(
-            json!({
-                "id": "Id1",
-                "name": "The Name",
-                "extra": "Extra Info",
-                "one-field": "Part of Simple",
-                "info2": "More Info 2",
-            }),
-            &API_METHOD_CREATE_THING,
-            &mut RpcEnv,
-        )
-        .expect("create_thing should work");
-        assert_eq!(value, "Id1");
-        assert_eq!(
-            unsafe { &TEST_OBJECT },
-            &Some(Creatable {
-                id: "Id1".to_string(),
-                name: "The Name".to_string(),
-                info: None,
-                info2: Some("More Info 2".to_string()),
-                complex: Complex {
-                    extra: "Extra Info".to_string(),
-                    simple: Simple {
-                        one_field: "Part of Simple".to_string(),
-                        opt: None,
-                    },
-                },
-            }),
-        );
-
-        let _ = api_function_update_thing(
-            json!({
-                "id": "Poop",
-            }),
-            &API_METHOD_UPDATE_THING,
-            &mut RpcEnv,
-        )
-        .expect_err("shouldn't be allowed to update the ID");
-
-        let _ = api_function_update_thing(
-            json!({
-                "info": "Updated Info",
-                "delete": ["info2"],
-            }),
-            &API_METHOD_UPDATE_THING,
-            &mut RpcEnv,
-        )
-        .expect("should be allowed to update the optional field");
-
-        assert_eq!(
-            unsafe { &TEST_OBJECT },
-            &Some(Creatable {
-                id: "Id1".to_string(),
-                name: "The Name".to_string(),
-                info: Some("Updated Info".to_string()),
-                info2: None,
-                complex: Complex {
-                    extra: "Extra Info".to_string(),
-                    simple: Simple {
-                        one_field: "Part of Simple".to_string(),
-                        opt: None,
-                    },
-                },
-            }),
-        );
-
-        let _ = api_function_update_thing(
-            json!({
-                "extra": "Partial flatten update",
-            }),
-            &API_METHOD_UPDATE_THING,
-            &mut RpcEnv,
-        )
-        .expect("should be allowed to update the parts of a flattened field");
-        assert_eq!(
-            unsafe { &TEST_OBJECT },
-            &Some(Creatable {
-                id: "Id1".to_string(),
-                name: "The Name".to_string(),
-                info: Some("Updated Info".to_string()),
-                info2: None,
-                complex: Complex {
-                    extra: "Partial flatten update".to_string(),
-                    simple: Simple {
-                        one_field: "Part of Simple".to_string(),
-                        opt: None,
-                    },
-                },
-            }),
-        );
-
-        let _ = api_function_update_thing(
-            json!({
-                "opt": "Deeply nested optional update.",
-            }),
-            &API_METHOD_UPDATE_THING,
-            &mut RpcEnv,
-        )
-        .expect("should be allowed to update the parts of a deeply nested struct");
-        assert_eq!(
-            unsafe { &TEST_OBJECT },
-            &Some(Creatable {
-                id: "Id1".to_string(),
-                name: "The Name".to_string(),
-                info: Some("Updated Info".to_string()),
-                info2: None,
-                complex: Complex {
-                    extra: "Partial flatten update".to_string(),
-                    simple: Simple {
-                        one_field: "Part of Simple".to_string(),
-                        opt: Some("Deeply nested optional update.".to_string()),
-                    },
-                },
-            }),
-        );
 
-        let _ = api_function_update_thing(
-            json!({
-                "delete": ["opt"],
-            }),
-            &API_METHOD_UPDATE_THING,
-            &mut RpcEnv,
-        )
-        .expect("should be allowed to remove parts of a deeply nested struct");
-        assert_eq!(
-            unsafe { &TEST_OBJECT },
-            &Some(Creatable {
-                id: "Id1".to_string(),
-                name: "The Name".to_string(),
-                info: Some("Updated Info".to_string()),
-                info2: None,
-                complex: Complex {
-                    extra: "Partial flatten update".to_string(),
-                    simple: Simple {
-                        one_field: "Part of Simple".to_string(),
-                        opt: None,
-                    },
-                },
-            }),
-        );
-    }
+    /// A field which should not appear in the updater.
+    #[updater(skip)]
+    not_in_updater: String,
 }
index 4b5ce06e9449b33b2a5e55d7227e94373a061623..5b7e3d5d82c042dcadd8687e38cbe537c8a7c487 100644 (file)
@@ -1531,11 +1531,6 @@ pub trait Updatable: Sized {
     /// 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>;
 }
 
 #[cfg(feature = "api-macro")]
@@ -1549,23 +1544,8 @@ macro_rules! basic_updatable {
     ($($ty:ty)*) => {
         $(
             impl Updatable for $ty {
-                type Updater = Option<$ty>;
+                type Updater = Option<Self>;
                 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"))
-                }
             }
         )*
     };
@@ -1578,24 +1558,6 @@ where
 {
     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. This trait is *not* implemented for an api "base" type