diff --git a/Cargo.toml b/Cargo.toml index d445634..a0ac8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ keywords = ["serialization", "version"] proc-macro2 = "1.0.47" quote = "1.0.21" syn = { version = "1.0.13", features=["full", "extra-traits"]} +semver = "1.0.16" [lib] proc-macro = true diff --git a/src/common.rs b/src/common.rs index a990f86..07a097c 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,6 +1,8 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +use std::collections::BTreeMap; + /// An interface for generating serialzer and deserializers based on /// field descriptions. pub trait Descriptor { @@ -8,60 +10,67 @@ pub trait Descriptor { fn generate_serializer(&self) -> proc_macro2::TokenStream; /// Returns the deserializer code block as a token stream. fn generate_deserializer(&self) -> proc_macro2::TokenStream; - /// Returns the curent version. - fn version(&self) -> u16; - /// Returns the type name as string. - fn ty(&self) -> String; } /// Describes a structure and it's fields. pub(crate) struct GenericDescriptor { // The structure type identifier. pub ty: syn::Ident, - pub version: u16, + pub versions: BTreeMap>, pub fields: Vec, } // A trait that defines an interface to check if a certain field // exists at a specified version. pub(crate) trait Exists { - fn exists_at(&self, version: u16) -> bool { - // All fields have a start version. - // Some field do not have an end version specified. - version >= self.start_version() - && (0 == self.end_version() || (self.end_version() > 0 && version < self.end_version())) + fn exists_at(&self, minor: u64, patch: u64) -> bool { + let start = self.start_version(); + let end = self.end_version(); + + let default_start = || -> bool { + if start.is_empty() { + return true; + } else if start.iter().all(|x| minor < x.minor) { + return false; + } else if start.iter().all(|x| minor > x.minor) { + return true; + } + return false; + }; + let default_end = || -> bool { + if end.is_empty() { + return true; + } else if end.iter().all(|x| minor < x.minor) { + return true; + } else if end.iter().all(|x| minor > x.minor) { + return false; + } + return false; + }; + + start + .iter() + .find(|list| list.minor == minor) + .map_or_else(default_start, |found| patch >= found.patch) + && end + .iter() + .find(|list| list.minor == minor) + .map_or_else(default_end, |found| patch < found.patch) + } + + fn list_versions(&self) -> Vec { + let mut rets = self.start_version().to_owned(); + rets.append(&mut self.end_version().to_owned()); + rets.sort(); + rets.dedup(); + rets } - fn start_version(&self) -> u16; - fn end_version(&self) -> u16; + fn start_version(&self) -> &[semver::Version]; + fn end_version(&self) -> &[semver::Version]; } // A trait that defines an interface for exposing a field type. pub(crate) trait FieldType { fn ty(&self) -> syn::Type; } - -#[cfg(test)] -mod tests { - use super::Exists; - - #[test] - fn test_exists_at() { - impl Exists for u32 { - fn start_version(&self) -> u16 { - 3 - } - - fn end_version(&self) -> u16 { - 5 - } - } - - let test = 1234; - assert!(!test.exists_at(2)); - assert!(test.exists_at(3)); - assert!(test.exists_at(4)); - assert!(!test.exists_at(5)); - assert!(!test.exists_at(6)); - } -} diff --git a/src/descriptors/enum_desc.rs b/src/descriptors/enum_desc.rs index 7b59cae..6f16688 100644 --- a/src/descriptors/enum_desc.rs +++ b/src/descriptors/enum_desc.rs @@ -3,8 +3,9 @@ use common::{Descriptor, GenericDescriptor}; use fields::enum_variant::*; -use helpers::compute_version; +use helpers::collect_version; use quote::quote; +use std::collections::BTreeMap; pub(crate) type EnumDescriptor = GenericDescriptor; @@ -12,24 +13,16 @@ impl Descriptor for EnumDescriptor { fn generate_serializer(&self) -> proc_macro2::TokenStream { let mut versioned_serializers = proc_macro2::TokenStream::new(); - for i in 1..=self.version { - let mut versioned_serializer = proc_macro2::TokenStream::new(); + for field in &self.fields { + versioned_serializers.extend(field.generate_serializer(u64::MAX, u64::MAX)); + } - for field in &self.fields { - versioned_serializer.extend(field.generate_serializer(i)); + // Generate the serializer for current version only. + quote! { + match self { + #versioned_serializers } - - // Generate the match arm for version `i` serializer. - versioned_serializers.extend(quote! { - #i => { - match self { - #versioned_serializer - } - } - }); } - - versioned_serializers } // Versioned/semantic deserialization is not implemented for enums. @@ -37,37 +30,30 @@ impl Descriptor for EnumDescriptor { let mut versioned_deserializers = proc_macro2::TokenStream::new(); for field in &self.fields { - versioned_deserializers.extend(field.generate_deserializer()); + versioned_deserializers.extend(field.generate_deserializer(u64::MAX, u64::MAX)); } quote! { - let variant_index = ::deserialize(&mut reader, version_map, app_version)?; + let source = version_map.get_crate_version(env!("CARGO_PKG_NAME"))?; + let variant_index = ::deserialize(&mut reader, version_map)?; match variant_index { #versioned_deserializers x => return Err(VersionizeError::Deserialize(format!("Unknown variant_index {}", x))) } } } - - fn version(&self) -> u16 { - self.version - } - - fn ty(&self) -> String { - self.ty.to_string() - } } impl EnumDescriptor { pub fn new(input: &syn::DataEnum, ident: syn::Ident) -> Self { let mut descriptor = EnumDescriptor { ty: ident, - version: 1, + versions: BTreeMap::new(), fields: vec![], }; descriptor.parse_enum_variants(&input.variants); - descriptor.version = compute_version(&descriptor.fields); + descriptor.versions = collect_version(&descriptor.fields); descriptor } @@ -76,8 +62,7 @@ impl EnumDescriptor { variants: &syn::punctuated::Punctuated, ) { for (index, variant) in variants.iter().enumerate() { - self.fields - .push(EnumVariant::new(self.version, variant, index as u32)); + self.fields.push(EnumVariant::new(variant, index as u32)); } } } diff --git a/src/descriptors/struct_desc.rs b/src/descriptors/struct_desc.rs index 7785114..d8222c4 100644 --- a/src/descriptors/struct_desc.rs +++ b/src/descriptors/struct_desc.rs @@ -3,103 +3,102 @@ use common::{Descriptor, GenericDescriptor}; use fields::struct_field::*; -use helpers::compute_version; +use helpers::collect_version; use quote::{format_ident, quote}; +use std::collections::BTreeMap; pub(crate) type StructDescriptor = GenericDescriptor; impl Descriptor for StructDescriptor { fn generate_serializer(&self) -> proc_macro2::TokenStream { - let mut versioned_serializers = proc_macro2::TokenStream::new(); - - for i in 1..=self.version { - let mut versioned_serializer = proc_macro2::TokenStream::new(); - let mut semantic_serializer = proc_macro2::TokenStream::new(); - - // Generate field and semantic serializers for all fields. - // Not all fields have semantic serializers defined and some fields - // might be missing in version `i`. In these cases the generate_serializer() and - // generate_semantic_serializer() will return an empty token stream. - for field in &self.fields { - versioned_serializer.extend(field.generate_serializer(i)); - semantic_serializer.extend(field.generate_semantic_serializer(i)); - } - - // Generate the match arm for version `i`. - versioned_serializers.extend(quote! { - #i => { - #semantic_serializer - #versioned_serializer - } - }); + let mut versioned_serializer = proc_macro2::TokenStream::new(); + let mut semantic_serializer = proc_macro2::TokenStream::new(); + + // Generate field and semantic serializers for all fields. + // Not all fields have semantic serializers defined and some fields + // might be missing in version `i`. In these cases the generate_serializer() and + // generate_semantic_serializer() will return an empty token stream. + for field in &self.fields { + versioned_serializer.extend(field.generate_serializer(u64::MAX, u64::MAX)); + semantic_serializer.extend(field.generate_semantic_serializer(u64::MAX, u64::MAX)); } - versioned_serializers + quote! { + #semantic_serializer + #versioned_serializer + } } fn generate_deserializer(&self) -> proc_macro2::TokenStream { let mut versioned_deserializers = proc_macro2::TokenStream::new(); let struct_ident = format_ident!("{}", self.ty); - for i in 1..=self.version { - let mut versioned_deserializer = proc_macro2::TokenStream::new(); - let mut semantic_deserializer = proc_macro2::TokenStream::new(); - - // Generate field and semantic deserializers for all fields. - // Not all fields have semantic deserializers defined and some fields - // might be missing in version `i`. In these cases the generate_deserializer() and - // generate_semantic_deserializer() will return an empty token stream. - for field in &self.fields { - versioned_deserializer.extend(field.generate_deserializer(i)); - semantic_deserializer.extend(field.generate_semantic_deserializer(i)); + let mut first_minor = true; + let mut last_minor = 0; + for (minor, patchs) in &self.versions { + if first_minor && *minor != 0 { + let first_range = minor.wrapping_sub(1); + let field_deserializers = self.generate_field_deserializer(first_range, 0); + versioned_deserializers.extend(quote! { + (0..=#first_range, _) => { + #field_deserializers + } + }); + first_minor = false; } - - // Generate the match arm for version `i`. - // - // The semantic deserialization functions will be called after the object is constructed - // using the previously generated field deserializers. + let mut start = 0; + for patch in patchs { + // skip 0 + if *patch == start { + continue; + } + let end = patch - 1; + let field_deserializers = self.generate_field_deserializer(*minor, end); + versioned_deserializers.extend(quote! { + (#minor, #start..=#end) => { + #field_deserializers + } + }); + start = *patch; + } + let field_deserializers = self.generate_field_deserializer(*minor, start); versioned_deserializers.extend(quote! { - #i => { - let mut object = #struct_ident { - #versioned_deserializer - }; - #semantic_deserializer - Ok(object) + (#minor, #start..) => { + #field_deserializers } }); + last_minor = *minor + 1; } + let field_deserializers = self.generate_field_deserializer(last_minor, 0); + versioned_deserializers.extend(quote! { + (#last_minor.., _) => { + #field_deserializers + } + }); // 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()); - match version { + // NOTE: Unsupport source.minor less then cur.minor - 2 + let source = version_map.get_crate_version(env!("CARGO_PKG_NAME"))?; + match (source.minor, source.patch) { #versioned_deserializers - _ => panic!("Unknown {:?} version {}.", ::type_id(), version) } } } - - fn version(&self) -> u16 { - self.version - } - - fn ty(&self) -> String { - self.ty.to_string() - } } impl StructDescriptor { pub fn new(input: &syn::DataStruct, ident: syn::Ident) -> Self { let mut descriptor = StructDescriptor { ty: ident, - version: 1, // struct start at version 1. + versions: BTreeMap::new(), fields: vec![], }; // Fills self.fields. descriptor.parse_struct_fields(&input.fields); - descriptor.version = compute_version(&descriptor.fields); + descriptor.versions = collect_version(&descriptor.fields); descriptor } @@ -108,10 +107,33 @@ impl StructDescriptor { syn::Fields::Named(ref named_fields) => { let pairs = named_fields.named.pairs(); for field in pairs { - self.fields.push(StructField::new(self.version, field)); + self.fields.push(StructField::new(field)); } } _ => panic!("Only named fields are supported."), } } + + fn generate_field_deserializer(&self, minor: u64, patch: u64) -> proc_macro2::TokenStream { + let struct_ident = format_ident!("{}", self.ty); + let mut versioned_deserializer = proc_macro2::TokenStream::new(); + let mut semantic_deserializer = proc_macro2::TokenStream::new(); + + // Generate field and semantic deserializers for all fields. + // Not all fields have semantic deserializers defined and some fields + // might be missing in version `i`. In these cases the generate_deserializer() and + // generate_semantic_deserializer() will return an empty token stream. + for field in &self.fields { + versioned_deserializer.extend(field.generate_deserializer(minor, patch)); + semantic_deserializer.extend(field.generate_semantic_deserializer(minor, patch)); + } + + quote! { + let mut object = #struct_ident { + #versioned_deserializer + }; + #semantic_deserializer + Ok(object) + } + } } diff --git a/src/fields/enum_variant.rs b/src/fields/enum_variant.rs index 4fcc77a..3b1e67c 100644 --- a/src/fields/enum_variant.rs +++ b/src/fields/enum_variant.rs @@ -1,35 +1,35 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use super::super::DEFAULT_FN; +use super::super::{DEFAULT_FN, END_VERSION, START_VERSION}; use common::Exists; -use helpers::{get_end_version, get_ident_attr, get_start_version, parse_field_attributes}; +use helpers::{get_ident_attr, get_version, parse_field_attributes}; use quote::{format_ident, quote}; -use std::collections::hash_map::HashMap; +use std::collections::HashMap; #[derive(Debug, Eq, PartialEq, Clone)] pub(crate) struct EnumVariant { ident: syn::Ident, ty: Vec, - start_version: u16, // Bincode uses u32 instead of usize also. variant_index: u32, - end_version: u16, + start_version: Vec, + end_version: Vec, attrs: HashMap, } impl Exists for EnumVariant { - fn start_version(&self) -> u16 { - self.start_version + fn start_version(&self) -> &[semver::Version] { + &self.start_version } - fn end_version(&self) -> u16 { - self.end_version + fn end_version(&self) -> &[semver::Version] { + &self.end_version } } impl EnumVariant { - pub fn new(base_version: u16, ast_variant: &syn::Variant, variant_index: u32) -> Self { + pub fn new(ast_variant: &syn::Variant, variant_index: u32) -> Self { let attrs = parse_field_attributes(&ast_variant.attrs); let ty = match &ast_variant.fields { @@ -45,19 +45,18 @@ impl EnumVariant { ident: ast_variant.ident.clone(), ty, variant_index, - // Set base version. - start_version: get_start_version(&attrs).unwrap_or(base_version), - end_version: get_end_version(&attrs).unwrap_or_default(), + start_version: get_version(START_VERSION, &attrs), + end_version: get_version(END_VERSION, &attrs), attrs, } } // Emits code that serializes an enum variant. - pub fn generate_serializer(&self, target_version: u16) -> proc_macro2::TokenStream { + pub fn generate_serializer(&self, minor: u64, patch: u64) -> proc_macro2::TokenStream { let field_ident = &self.ident; let variant_index = self.variant_index; - if !self.exists_at(target_version) { + if !self.exists_at(minor, patch) { if let Some(default_fn_ident) = get_ident_attr(&self.attrs, DEFAULT_FN) { let field_type_ident = if self.ty.is_empty() { quote! { Self::#field_ident => } @@ -81,7 +80,7 @@ impl EnumVariant { let data_ident = format_ident!("data_{}", index); data_tuple.extend(quote!(#data_ident,)); serialize_data.extend(quote! { - Versionize::serialize(#data_ident, writer, version_map, app_version)?; + Versionize::serialize(#data_ident, &mut writer, version_map)?; }); } @@ -89,14 +88,14 @@ impl EnumVariant { quote! { Self::#field_ident => { let index: u32 = #variant_index; - Versionize::serialize(&index, writer, version_map, app_version)?; + Versionize::serialize(&index, &mut writer, version_map)?; }, } } else { quote! { Self::#field_ident(#data_tuple) => { let index: u32 = #variant_index; - Versionize::serialize(&index, writer, version_map, app_version)?; + Versionize::serialize(&index, &mut writer, version_map)?; #serialize_data }, } @@ -123,7 +122,7 @@ impl EnumVariant { data_tuple.extend(quote!(#data_ident,)); deserialize_data.extend( quote! { - let #data_ident = <#data_type as Versionize>::deserialize(&mut reader, version_map, app_version)?; + let #data_ident = <#data_type as Versionize>::deserialize(&mut reader, version_map)?; } ); } @@ -140,9 +139,9 @@ impl EnumVariant { quote! { { // Call user defined fn to provide a variant that exists in target version. - let new_variant = self.#default_fn_ident(version)?; + let new_variant = self.#default_fn_ident(current)?; // The new_variant will serialize its index and data. - new_variant.serialize(writer, version_map, app_version)?; + new_variant.serialize(&mut writer, version_map)?; }, } } diff --git a/src/fields/struct_field.rs b/src/fields/struct_field.rs index 982a50e..b52317d 100644 --- a/src/fields/struct_field.rs +++ b/src/fields/struct_field.rs @@ -1,9 +1,9 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use super::super::{DEFAULT_FN, SEMANTIC_DE_FN, SEMANTIC_SER_FN}; +use super::super::{DEFAULT_FN, END_VERSION, SEMANTIC_DE_FN, SEMANTIC_SER_FN, START_VERSION}; use common::{Exists, FieldType}; -use helpers::{get_end_version, get_ident_attr, get_start_version, parse_field_attributes}; +use helpers::{get_ident_attr, get_version, parse_field_attributes}; use quote::{format_ident, quote}; use std::collections::hash_map::HashMap; @@ -11,18 +11,18 @@ use std::collections::hash_map::HashMap; pub(crate) struct StructField { ty: syn::Type, name: String, - start_version: u16, - end_version: u16, + start_version: Vec, + end_version: Vec, attrs: HashMap, } impl Exists for StructField { - fn start_version(&self) -> u16 { - self.start_version + fn start_version(&self) -> &[semver::Version] { + &self.start_version } - fn end_version(&self) -> u16 { - self.end_version + fn end_version(&self) -> &[semver::Version] { + &self.end_version } } @@ -33,81 +33,82 @@ impl FieldType for StructField { } impl StructField { - pub fn new( - base_version: u16, - ast_field: syn::punctuated::Pair<&syn::Field, &syn::token::Comma>, - ) -> Self { + pub fn new(ast_field: syn::punctuated::Pair<&syn::Field, &syn::token::Comma>) -> Self { let attrs = parse_field_attributes(&ast_field.value().attrs); StructField { ty: ast_field.value().ty.clone(), name: ast_field.value().ident.as_ref().unwrap().to_string(), - start_version: get_start_version(&attrs).unwrap_or(base_version), - end_version: get_end_version(&attrs).unwrap_or_default(), + start_version: get_version(START_VERSION, &attrs), + end_version: get_version(END_VERSION, &attrs), attrs, } } - pub fn generate_semantic_serializer(&self, target_version: u16) -> proc_macro2::TokenStream { + pub fn generate_semantic_serializer(&self, minor: u64, patch: u64) -> proc_macro2::TokenStream { // Generate semantic serializer for this field only if it does not exist in target_version. - if !self.exists_at(target_version) { + if !self.exists_at(minor, patch) { if let Some(semantic_ser_fn) = get_ident_attr(&self.attrs, SEMANTIC_SER_FN) { return quote! { - copy_of_self.#semantic_ser_fn(version)?; + copy_of_self.#semantic_ser_fn(current)?; }; } } quote! {} } - pub fn generate_semantic_deserializer(&self, source_version: u16) -> proc_macro2::TokenStream { + pub fn generate_semantic_deserializer( + &self, + minor: u64, + patch: u64, + ) -> proc_macro2::TokenStream { // Generate semantic deserializer for this field only if it does not exist in source_version. - if !self.exists_at(source_version) { + if !self.exists_at(minor, patch) { if let Some(semantic_de_fn) = get_ident_attr(&self.attrs, SEMANTIC_DE_FN) { // Object is an instance of the structure. return quote! { - object.#semantic_de_fn(version)?; + // - `source` is set to version_map.get_crate_version(); + object.#semantic_de_fn(source)?; }; } } quote! {} } - pub fn generate_serializer(&self, target_version: u16) -> proc_macro2::TokenStream { + pub fn generate_serializer(&self, minor: u64, patch: u64) -> proc_macro2::TokenStream { let field_ident = format_ident!("{}", self.name); // Generate serializer for this field only if it exists in target_version. - if !self.exists_at(target_version) { + if !self.exists_at(minor, patch) { return proc_macro2::TokenStream::new(); } match &self.ty { syn::Type::Array(_) => quote! { for element in copy_of_self.#field_ident.iter() { - Versionize::serialize(element, writer, version_map, app_version)?; + Versionize::serialize(element, &mut writer, version_map)?; } }, syn::Type::Path(_) => quote! { - Versionize::serialize(©_of_self.#field_ident, writer, version_map, app_version)?; + Versionize::serialize(©_of_self.#field_ident, &mut writer, version_map)?; }, syn::Type::Reference(_) => quote! { - copy_of_self.#field_ident.serialize(writer, version_map, app_version)?; + copy_of_self.#field_ident.serialize(&mut writer, version_map)?; }, _ => panic!("Unsupported field type {:?}", self.ty), } } - pub fn generate_deserializer(&self, source_version: u16) -> proc_macro2::TokenStream { + pub fn generate_deserializer(&self, minor: u64, patch: u64) -> proc_macro2::TokenStream { let field_ident = format_ident!("{}", self.name); // If the field does not exist in source version, use default annotation or Default trait. - if !self.exists_at(source_version) { + if !self.exists_at(minor, patch) { if let Some(default_fn) = get_ident_attr(&self.attrs, DEFAULT_FN) { return quote! { // The default_fn is called with source version of the struct: - // - `version` is set to version_map.get_type_version(app_version, Self::type_id()); - // - `app_version` is source application version. - #field_ident: Self::#default_fn(version), + // - `source` is set to version_map.get_crate_version(); + #field_ident: Self::#default_fn(source), }; } else { return quote! { #field_ident: Default::default(), }; @@ -142,10 +143,10 @@ impl StructField { } } syn::Type::Path(_) => quote! { - #field_ident: <#ty as Versionize>::deserialize(&mut reader, version_map, app_version)?, + #field_ident: <#ty as Versionize>::deserialize(&mut reader, version_map)?, }, syn::Type::Reference(_) => quote! { - #field_ident: <#ty as Versionize>::deserialize(&mut reader, version_map, app_version)?, + #field_ident: <#ty as Versionize>::deserialize(&mut reader, version_map)?, }, _ => panic!("Unsupported field type {:?}", self.ty), } @@ -162,7 +163,7 @@ impl StructField { #field_ident: { let mut array = [#array_type_token::default() ; #array_len]; for i in 0..#array_len { - array[i] = <#array_type_token as Versionize>::deserialize(&mut reader, version_map, app_version)?; + array[i] = <#array_type_token as Versionize>::deserialize(&mut reader, version_map)?; } array }, diff --git a/src/helpers.rs b/src/helpers.rs index a04c46f..3476028 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,11 +1,10 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use super::{ATTRIBUTE_NAME, END_VERSION, START_VERSION}; +use super::ATTRIBUTE_NAME; use common::Exists; use quote::format_ident; -use std::cmp::max; -use std::collections::hash_map::HashMap; +use std::collections::{BTreeMap, HashMap}; // Returns a string literal attribute as an Ident. pub(crate) fn get_ident_attr( @@ -20,24 +19,28 @@ pub(crate) fn get_ident_attr( }) } -pub(crate) fn get_start_version(attrs: &HashMap) -> Option { - if let Some(start_version) = attrs.get(START_VERSION) { - return Some(match start_version { - syn::Lit::Int(lit_int) => lit_int.base10_parse().unwrap(), - _ => panic!("Field start/end version number must be an integer"), - }); +pub(crate) fn get_version(key: &str, attrs: &HashMap) -> Vec { + if let Some(version) = attrs.get(key) { + return match version { + syn::Lit::Str(lit_str) => parse_version(&lit_str.value()), + _ => panic!("Field start/end version must be an semver"), + }; } - None + Vec::new() } -pub(crate) fn get_end_version(attrs: &HashMap) -> Option { - if let Some(start_version) = attrs.get(END_VERSION) { - return Some(match start_version { - syn::Lit::Int(lit_int) => lit_int.base10_parse().unwrap(), - _ => panic!("Field start/end version number must be an integer"), - }); - } - None +pub(crate) fn parse_version(versions: &str) -> Vec { + versions + .split(',') + .filter(|x| !x.is_empty()) + .map(|version| { + let v = semver::Version::parse(version.trim()).expect("parse semver"); + if !v.pre.is_empty() || !v.build.is_empty() { + panic!("Unsupported pre-release and build metadata."); + } + v + }) + .collect() } // Returns an attribute hash_map constructed by processing a vector of syn::Attribute. @@ -70,14 +73,223 @@ pub(crate) fn parse_field_attributes(attributes: &[syn::Attribute]) -> HashMap(fields: &[T]) -> u16 +pub(crate) fn collect_version(fields: &[T]) -> BTreeMap> where T: Exists, { - let mut version = 0; + let mut vers = vec![]; for field in fields { - version = max(version, max(field.start_version(), field.end_version())); + vers.append(&mut field.list_versions()); + } + vers.sort(); + vers.dedup(); + let mut rets: BTreeMap> = BTreeMap::new(); + for v in &vers { + if let Some(p) = rets.get_mut(&v.minor) { + p.push(v.patch); + } else { + rets.insert(v.minor, vec![v.patch]); + } + } + rets +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_version() { + let versions = parse_version("2.7.14,2.8.4"); + assert_eq!(versions[0], semver::Version::new(2, 7, 14)); + assert_eq!(versions[1], semver::Version::new(2, 8, 4)); + + let versions = parse_version(" 2.7.14, 2.8.4 "); + assert_eq!(versions[0], semver::Version::new(2, 7, 14)); + assert_eq!(versions[1], semver::Version::new(2, 8, 4)); + } + + #[test] + #[should_panic(expected = "Unsupported pre-release and build metadata.")] + fn test_parse_version_panic_1() { + parse_version("2.7.14-alpha"); + } + + #[test] + #[should_panic(expected = "parse semver")] + fn test_parse_version_panic_2() { + parse_version("aaa"); + } + + struct TestField { + start: Vec, + end: Vec, + } + + impl Exists for TestField { + fn start_version(&self) -> &[semver::Version] { + &self.start + } + fn end_version(&self) -> &[semver::Version] { + &self.end + } + } + + #[test] + fn test_collect_version() { + let vers = vec![ + TestField { + start: parse_version("2.7.7,2.8.3"), + end: parse_version("2.7.11, 2.8.8"), + }, + TestField { + start: parse_version("2.8.0"), + end: parse_version("2.8.8"), + }, + ]; + let rets = collect_version(&vers); + assert_eq!( + rets, + BTreeMap::from([(7, vec![7, 11]), (8, vec![0, 3, 8]),]) + ); + + assert_eq!( + collect_version(&vec![ + TestField { + start: parse_version("2.7.0"), + end: vec![], + }, + TestField { + start: parse_version("2.7.3"), + end: vec![], + }, + TestField { + start: parse_version("2.7.4"), + end: parse_version("2.7.5"), + }, + TestField { + start: parse_version("2.8.0"), + end: vec![], + }, + TestField { + start: parse_version("2.7.8, 2.8.3"), + end: vec![], + }, + TestField { + start: parse_version("2.7.8, 2.8.4"), + end: vec![], + }, + TestField { + start: parse_version("2.9.0"), + end: vec![], + }, + ]), + BTreeMap::from([(7, vec![0, 3, 4, 5, 8]), (8, vec![0, 3, 4]), (9, vec![0])]) + ); + } + + #[test] + fn test_exists_at() { + let field = TestField { + start: parse_version("2.7.7,2.8.3"), + end: parse_version("2.7.11, 2.8.8"), + }; + + assert_eq!(field.exists_at(0, 0), false); + assert_eq!(field.exists_at(7, 0), false); + assert_eq!(field.exists_at(7, 7), true); + assert_eq!(field.exists_at(7, 10), true); + assert_eq!(field.exists_at(7, 11), false); + assert_eq!(field.exists_at(8, 0), false); + assert_eq!(field.exists_at(8, 3), true); + assert_eq!(field.exists_at(8, 7), true); + assert_eq!(field.exists_at(8, 8), false); + assert_eq!(field.exists_at(9, 0), false); + assert_eq!(field.exists_at(999, 9999), false); + + let field = TestField { + start: parse_version(""), + end: parse_version(""), + }; + assert_eq!(field.exists_at(0, 0), true); + assert_eq!(field.exists_at(7, 0), true); + assert_eq!(field.exists_at(7, 99), true); + assert_eq!(field.exists_at(8, 0), true); + assert_eq!(field.exists_at(8, 99), true); + assert_eq!(field.exists_at(9, 0), true); + assert_eq!(field.exists_at(999, 9999), true); + + let field = TestField { + start: parse_version("2.8.1"), + end: parse_version(""), + }; + assert_eq!(field.exists_at(0, 0), false); + assert_eq!(field.exists_at(7, 0), false); + assert_eq!(field.exists_at(7, 999), false); + assert_eq!(field.exists_at(8, 0), false); + assert_eq!(field.exists_at(8, 1), true); + assert_eq!(field.exists_at(8, 999), true); + assert_eq!(field.exists_at(9, 0), true); + assert_eq!(field.exists_at(999, 9999), true); + + let field = TestField { + start: parse_version(""), + end: parse_version("2.8.8"), + }; + assert_eq!(field.exists_at(0, 0), true); + assert_eq!(field.exists_at(7, 0), true); + assert_eq!(field.exists_at(7, 999), true); + assert_eq!(field.exists_at(8, 0), true); + assert_eq!(field.exists_at(8, 7), true); + assert_eq!(field.exists_at(8, 8), false); + assert_eq!(field.exists_at(8, 999), false); + assert_eq!(field.exists_at(9, 0), false); + assert_eq!(field.exists_at(999, 9999), false); + + let field = TestField { + start: parse_version(""), + end: parse_version("2.7.3, 2.8.8"), + }; + assert_eq!(field.exists_at(0, 0), true); + assert_eq!(field.exists_at(7, 2), true); + assert_eq!(field.exists_at(7, 3), false); + assert_eq!(field.exists_at(7, 999), false); + assert_eq!(field.exists_at(8, 0), true); + assert_eq!(field.exists_at(8, 7), true); + assert_eq!(field.exists_at(8, 8), false); + assert_eq!(field.exists_at(8, 999), false); + assert_eq!(field.exists_at(9, 0), false); + assert_eq!(field.exists_at(999, 9999), false); + + let field = TestField { + start: parse_version("2.7.7, 2.9.3, 2.11.10"), + end: parse_version("2.7.11, 2.9.8, 2.11.15"), + }; + + assert_eq!(field.exists_at(0, 0), false); + assert_eq!(field.exists_at(7, 0), false); + assert_eq!(field.exists_at(7, 7), true); + assert_eq!(field.exists_at(7, 10), true); + assert_eq!(field.exists_at(7, 11), false); + assert_eq!(field.exists_at(7, 999), false); + assert_eq!(field.exists_at(8, 0), false); + assert_eq!(field.exists_at(8, 5), false); + assert_eq!(field.exists_at(8, 999), false); + assert_eq!(field.exists_at(9, 0), false); + assert_eq!(field.exists_at(9, 2), false); + assert_eq!(field.exists_at(9, 3), true); + assert_eq!(field.exists_at(9, 7), true); + assert_eq!(field.exists_at(9, 8), false); + assert_eq!(field.exists_at(9, 999), false); + assert_eq!(field.exists_at(10, 0), false); + assert_eq!(field.exists_at(10, 5), false); + assert_eq!(field.exists_at(10, 999), false); + assert_eq!(field.exists_at(11, 0), false); + assert_eq!(field.exists_at(11, 9), false); + assert_eq!(field.exists_at(11, 10), true); + assert_eq!(field.exists_at(11, 14), true); + assert_eq!(field.exists_at(11, 15), false); + assert_eq!(field.exists_at(11, 99999), false); + assert_eq!(field.exists_at(999, 9999), false); } - version } diff --git a/src/lib.rs b/src/lib.rs index b987f77..9cb682f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,8 @@ pub(crate) const END_VERSION: &str = "end"; /// To facilitate version tolerant serialization "history metadata" is attached /// to the structure or enum. This is done by using the `version` attribute in /// their definition. In the below example a new field is added to the -/// structure starting with version 2: `#[version(start = 2)]`. +/// structure starting with version 0.3.1: `#[version(start = "0.3.1")]`. +/// This version is the same as the crate version. /// /// ```ignore /// extern crate versionize; @@ -72,15 +73,15 @@ pub(crate) const END_VERSION: &str = "end"; /// #[derive(Versionize)] /// struct Test { /// a: u32, -/// #[version(start = 2)] +/// #[version(start = "0.3.1")] /// b: u8, /// } /// ``` /// /// Multiple version annotations can be defined for a field, like for example: -/// `#[version(start = 2, end = 3)]`. Field was added in structure version 2 -/// and removed in version 3. The generated code will attempt to (de)serialize -/// this field only for version 2 of the structure. +/// `#[version(start = "0.3.1", end = "0.3.3")]`. Field was added in structure +/// v0.3.1 and removed in v0.3.3. The generated code will attempt to (de)serialize +/// this field only for v0.3.1 and v0.3.2 of the structure. /// /// ### Supported field attributes and usage /// @@ -104,7 +105,7 @@ pub(crate) const END_VERSION: &str = "end"; /// #[derive(Versionize)] /// struct TestStruct { /// a: u32, -/// #[version(start = 2, default_fn = "default_b")] +/// #[version(start = "0.3.2", default_fn = "default_b")] /// b: u8, /// } /// @@ -125,9 +126,9 @@ pub(crate) const END_VERSION: &str = "end"; /// start version of the structure when first defining them and can be later /// on removed from serialization logic by adding and end version. /// -/// For example: `#[version(start = 2, end = 4)]`. The field would be present -/// in the structure v2 and v3, but starting with v4 it would no longer be -/// serialized or deserialized. +/// For example: `#[version(start = "0.3.2", end = "0.3.4")]`. The field would +/// be present in the structure v0.3.2 and v0.3.3, but starting with v0.3.4 it +/// would no longer be serialized or deserialized. /// /// Once a field is removed, it can never be added again in a future version. /// @@ -154,7 +155,7 @@ pub(crate) const END_VERSION: &str = "end"; /// #[derive(Versionize)] /// struct SomeStruct { /// some_u32: u32, -/// #[version(start = 2, ser_fn = "ser_u16")] +/// #[version(start = "0.3.2", ser_fn = "ser_u16")] /// some_u16: u16, /// } /// @@ -192,7 +193,7 @@ pub(crate) const END_VERSION: &str = "end"; /// #[derive(Clone, Versionize)] /// struct SomeStruct { /// some_u32: u32, -/// #[version(start = 2, ser_fn = "ser_u16", de_fn = "de_u16")] +/// #[version(start = "0.3.2", ser_fn = "ser_u16", de_fn = "de_u16")] /// some_u16: u16, /// } /// @@ -228,34 +229,25 @@ 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()); + // Set the crate version with crate name. + let current = version_map.set_crate_version(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))?; // 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) - } + #versioned_serializer }; (quote! { impl Versionize for #ident #generics { - fn serialize(&self, writer: &mut W, version_map: &VersionMap, app_version: u16) -> VersionizeResult<()> { + fn serialize(&self, mut writer: W, version_map: &mut VersionMap) -> VersionizeResult<()> { #serializer Ok(()) } - fn deserialize(mut reader: &mut R, version_map: &VersionMap, app_version: u16) -> VersionizeResult { + fn deserialize(mut reader: R, version_map: &VersionMap) -> VersionizeResult { #deserializer } - - // Returns struct current version. - fn version() -> u16 { - #version - } } }).into() }