From b3a66cda3b36b914a07d1e80ab55eafbd5d18953 Mon Sep 17 00:00:00 2001 From: Andre Popovitch Date: Wed, 14 Aug 2024 17:46:56 -0500 Subject: [PATCH 1/2] Don't expect values that are not sent on the wire --- rust/candid/src/de.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index afe5b14a..29bd9614 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -96,14 +96,14 @@ impl<'de> IDLDeserialize<'de> { }; self.de.wire_type = ty.clone(); - let mut v = T::deserialize(&mut self.de).with_context(|| { + let mut v = T::deserialize(&mut self.de).with_context(|| -> String { if self.de.config.full_error_message || (text_size(&ty, MAX_TYPE_LEN).is_ok() && text_size(&expected_type, MAX_TYPE_LEN).is_ok()) { - format!("Fail to decode argument {ind} from {ty} to {expected_type}") + format!("Fail to decode argument {ind} from {ty} to {expected_type} when deserializing to {}", std::any::type_name::()) } else { - format!("Fail to decode argument {ind}") + format!("Fail to decode argument {ind} when deserializing to {}", std::any::type_name::()) } }); if self.de.config.full_error_message { @@ -985,8 +985,16 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { (TypeInner::Record(e), TypeInner::Record(w)) => { - let expect = e.clone().into(); - let wire = w.clone().into(); + // Remove expected keys that don't exist on the wire. + // Serde will still complain unless the user has explicitly allowed the field to not be populated. + let wire_keys = w.iter().cloned().map(|w| (w.id.get_id(), w)).collect::>(); + let kv = e.iter().filter_map(|e| { + let id = e.id.get_id(); + wire_keys.get(&id).map(|wire| (id, (e.clone(), wire.clone())))} + ).collect::>(); + + let wire = kv.iter().map(|(_, (_, w))| w.clone()).collect::>(); + let expect = kv.iter().map(|(_, (e, _))| e.clone()).collect::>(); let value = visitor.visit_map(Compound::new(self, Style::Struct { expect, wire }))?; Ok(value) From 5eafd4551eb5350efef043b8d33a88a0c766b035 Mon Sep 17 00:00:00 2001 From: Andre Popovitch Date: Wed, 14 Aug 2024 20:00:10 -0500 Subject: [PATCH 2/2] fix derive --- rust/candid_derive/src/derive.rs | 36 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/rust/candid_derive/src/derive.rs b/rust/candid_derive/src/derive.rs index 7f2838ff..36992f84 100644 --- a/rust/candid_derive/src/derive.rs +++ b/rust/candid_derive/src/derive.rs @@ -277,6 +277,7 @@ fn get_serde_meta_items(attr: &syn::Attribute) -> Result, ()> { struct Attributes { rename: Option, + aliases: Vec, with_bytes: bool, } @@ -284,6 +285,7 @@ fn get_attrs(attrs: &[syn::Attribute]) -> Attributes { use syn::Meta; let mut res = Attributes { rename: None, + aliases: Vec::new(), with_bytes: false, }; for item in attrs.iter().flat_map(get_serde_meta_items).flatten() { @@ -310,6 +312,12 @@ fn get_attrs(attrs: &[syn::Attribute]) -> Attributes { } } } + // #[serde(alias = "foo")] + Meta::NameValue(m) if m.path.is_ident("alias") => { + if let Ok(lit) = get_lit_str(&m.value) { + res.aliases.push(lit.value()); + } + } // #[serde(with = "serde_bytes")] Meta::NameValue(m) if m.path.is_ident("with") => { if let Ok(lit) = get_lit_str(&m.value) { @@ -332,7 +340,7 @@ fn fields_from_ast( let mut fs: Vec<_> = fields .iter() .enumerate() - .map(|(i, field)| { + .flat_map(|(i, field)| { let attrs = get_attrs(&field.attrs); let (real_ident, renamed_ident, hash) = match field.ident { Some(ref ident) => { @@ -351,13 +359,29 @@ fn fields_from_ast( } None => (Ident::Unnamed(i as u32), Ident::Unnamed(i as u32), i as u32), }; - Field { - real_ident, + let ty = derive_type(&field.ty, custom_candid_path); + let with_bytes = attrs.with_bytes; + + // Create the main field + let main_field = Field { + real_ident: real_ident.clone(), renamed_ident, hash, - ty: derive_type(&field.ty, custom_candid_path), - with_bytes: attrs.with_bytes, - } + ty: ty.clone(), + with_bytes, + }; + + // Create alias fields + let alias_fields = attrs.aliases.into_iter().map(move |alias| Field { + real_ident: real_ident.clone(), + renamed_ident: Ident::Renamed(alias.clone()), + hash: idl_hash(&alias), + ty: ty.clone(), + with_bytes, + }); + + // Combine main field and alias fields + std::iter::once(main_field).chain(alias_fields) }) .collect(); let unique: BTreeSet<_> = fs.iter().map(|Field { hash, .. }| hash).collect();