diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a20156..8e6671c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# v0.1.2 + +- Added a safety check for enforcing that the VersionMaps provided for + (de)serialization are up-to-date with the latest versions of the structs. + # v0.1.1 - Removed "versionize" dependency. diff --git a/Cargo.toml b/Cargo.toml index f4f5a7d..2fd30ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "versionize_derive" -version = "0.1.1" +version = "0.1.2" license = "Apache-2.0" authors = ["Amazon Firecracker team "] description = "Implements the Versionize derive proc macro." diff --git a/src/descriptors/struct_desc.rs b/src/descriptors/struct_desc.rs index dbeac4d..5e050ac 100644 --- a/src/descriptors/struct_desc.rs +++ b/src/descriptors/struct_desc.rs @@ -3,7 +3,7 @@ use common::{Descriptor, GenericDescriptor}; use fields::struct_field::*; -use helpers::compute_version; +use helpers::{compute_version, latest_version_check}; use quote::{format_ident, quote}; pub(crate) type StructDescriptor = GenericDescriptor; @@ -71,13 +71,16 @@ impl Descriptor for StructDescriptor { // Generate code to map the app version to struct version and wrap the // deserializers with the `version` match. - quote! { - let version = version_map.get_type_version(app_version, ::type_id()); + let mut deserializer = proc_macro2::TokenStream::new(); + deserializer.extend(latest_version_check(self.version())); + deserializer.extend(quote! { match version { #versioned_deserializers _ => panic!("Unknown {:?} version {}.", ::type_id(), version) } - } + }); + + deserializer } fn version(&self) -> u16 { diff --git a/src/descriptors/union_desc.rs b/src/descriptors/union_desc.rs index 9a5723e..dc443a4 100644 --- a/src/descriptors/union_desc.rs +++ b/src/descriptors/union_desc.rs @@ -3,7 +3,7 @@ use common::{Descriptor, Exists, FieldType, GenericDescriptor}; use fields::union_field::*; -use helpers::compute_version; +use helpers::{compute_version, latest_version_check}; use quote::quote; pub(crate) type UnionDescriptor = GenericDescriptor; @@ -40,13 +40,16 @@ impl Descriptor for UnionDescriptor { }); } - quote! { - let version = version_map.get_type_version(app_version, Self::type_id()); + let mut deserializer = proc_macro2::TokenStream::new(); + deserializer.extend(latest_version_check(self.version())); + deserializer.extend(quote! { match version { #versioned_deserializers - _ => panic!("Unknown {:?} version {}.", Self::type_id(), version) + _ => panic!("Unknown {:?} version {}.", ::type_id(), version) } - } + }); + + deserializer } fn version(&self) -> u16 { diff --git a/src/helpers.rs b/src/helpers.rs index 02ba345..0a679d7 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -3,7 +3,7 @@ use super::{ATTRIBUTE_NAME, END_VERSION, START_VERSION}; use common::Exists; -use quote::format_ident; +use quote::{format_ident, quote}; use std::cmp::max; use std::collections::hash_map::HashMap; @@ -79,6 +79,17 @@ pub fn is_array(ty: &syn::Type) -> bool { } } +// Enforce that the latest VersionMap version is up-to-date with the latest struct version. +pub(crate) fn latest_version_check(current_version: u16) -> proc_macro2::TokenStream { + quote! { + // Get the struct version for the input app_version. + let version = version_map.get_type_version(app_version, ::type_id()); + if app_version == version_map.latest_version() && version != #current_version { + return Err(VersionizeError::VersionMapNotUpdated); + } + } +} + // Compute current struct version by finding the latest field change version. pub(crate) fn compute_version(fields: &[T]) -> u16 where diff --git a/src/lib.rs b/src/lib.rs index d479149..acdb695 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ use common::Descriptor; use descriptors::{ enum_desc::EnumDescriptor, struct_desc::StructDescriptor, union_desc::UnionDescriptor, }; +use helpers::latest_version_check; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; @@ -50,16 +51,18 @@ pub fn impl_versionize(input: TokenStream) -> proc_macro::TokenStream { let version = descriptor.version(); let versioned_serializer = descriptor.generate_serializer(); let deserializer = descriptor.generate_deserializer(); - let serializer = quote! { - // Get the struct version for the input app_version. - let version = version_map.get_type_version(app_version, ::type_id()); + + let mut serializer = proc_macro2::TokenStream::new(); + serializer.extend(latest_version_check(version)); + serializer.extend(quote! { // We will use this copy to perform semantic serialization. let mut copy_of_self = self.clone(); match version { #versioned_serializer _ => panic!("Unknown {:?} version {}.", &::type_id(), version) } - }; + }); + (quote! { impl Versionize for #ident #generics { fn serialize(&self, writer: &mut W, version_map: &VersionMap, app_version: u16) -> VersionizeResult<()> {