1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#![deny(missing_docs)]

//! Exports the `Versionize` derive proc macro that generates the Versionize
//! trait implementation.
//!
//! Versionize generates serialization and deserialization code only for
//! structures and enums.
//!
//! Supported primitives: u8, u16, u32, u64, usize, i8, i16, i32, i64, isize,
//! char, f32, f64, String, Vec<T>, Arrays up to 32 elements, Box<T>,
//! Wrapping<T>, Option<T>, FamStructWrapper<T>, and (T, U).
//!
//! Known issues and limitations:
//! - Union serialization is not supported via the `Versionize` proc macro.
//! - Implementing Versionize for non-repr(C) unions can result in undefined
//! behaviour and MUST be avoided.
//! - Versionize trait implementations for repr(C) unions must be backed by
//! extensive testing.
//! - Semantic serialization and deserialization is available only for structures.
//!
extern crate proc_macro;
extern crate proc_macro2;
extern crate quote;
extern crate syn;

mod common;
mod descriptors;
mod fields;
mod helpers;

use common::Descriptor;
use descriptors::{enum_desc::EnumDescriptor, struct_desc::StructDescriptor};
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

pub(crate) const ATTRIBUTE_NAME: &str = "version";

/// Struct annotation constants.
pub(crate) const DEFAULT_FN: &str = "default_fn";
pub(crate) const SEMANTIC_SER_FN: &str = "ser_fn";
pub(crate) const SEMANTIC_DE_FN: &str = "de_fn";
pub(crate) const START_VERSION: &str = "start";
pub(crate) const END_VERSION: &str = "end";

/// Generates serialization and deserialization code as an implementation of
/// the `Versionize` trait.
///
/// Different code paths are generated for each version of the structure or
/// enum. There is no limit enforced on the maximum number of structure
/// versions.
///
/// ### Struct and enum requirements
/// - all members or enum variants need to implement the `Versionize` trait
/// - no generics are being used (this is currenly a limitation)
///
/// ## Annotations
///
/// 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)]`.
///
/// ```ignore
/// extern crate versionize;
/// extern crate versionize_derive;
/// use versionize::{Versionize, VersionizeError, VersionizeResult};
/// use versionize_derive::Versionize;
///
/// #[derive(Versionize)]
/// struct Test {
///     a: u32,
///     #[version(start = 2)]
///     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.
///
/// ### Supported field attributes and usage
///
/// The `version` attribute accepts multiple key/value pairs to be specified in
/// order to support versioning, semantic serialization and default values for
/// fields. All of these are optional and a default behaviour is provided in
/// their absence.
///
/// #### default_fn
///
/// Provides an initialization value for a field when deserializing from an
/// older structure version which does not contain this field. If not specified
/// the `Default` trait isused to initialize the field.
///
/// ```ignore
/// extern crate versionize;
/// extern crate versionize_derive;
/// use versionize::{Versionize, VersionizeError, VersionizeResult};
/// use versionize_derive::Versionize;
///
/// #[derive(Versionize)]
/// struct TestStruct {
///     a: u32,
///     #[version(start = 2, default_fn = "default_b")]
///     b: u8,
/// }
///
/// impl TestStruct {
///     fn default_b(_source_version: u16) -> u8 {
///         12u8
///     }
/// }
/// ```
///
/// The function name needs to be specified as a string and its prototype must
/// take an u16 source version parameter and return a value of the same type as
/// as the field.
///
/// #### start/end
///
/// Defines the field version lifetime. Fields can be added by specifing the
/// 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.
///
/// Once a field is removed, it can never be added again in a future version.
///
/// #### ser_fn
/// * Not supported for enums. *
///
/// Defines a semantic serialization function for a field. The function needs
/// to be specified as a string and implemented as a method attached to
/// the structure. The prototype of the function is
/// `fn(&mut self, u16) -> VersionizeResult<()>`.
///
/// If defined, the method is called when the field is skipped from
/// serialization because it does not exist in the target version of the
/// structure. Its implementation can perform any mutation of `self` or return
/// an error to stop serialization. Intended usage is to implement semantic
/// translation or semantic validations.
///
/// ```ignore
/// extern crate versionize;
/// extern crate versionize_derive;
/// use versionize::{Versionize, VersionizeError, VersionizeResult};
/// use versionize_derive::Versionize;
///
/// #[derive(Versionize)]
/// struct SomeStruct {
///     some_u32: u32,
///     #[version(start = 2, ser_fn = "ser_u16")]
///     some_u16: u16,
/// }
///
/// impl SomeStruct {
///     fn ser_u16(&mut self, target_version: u16) -> VersionizeResult<()> {
///         self.some_u32 = self.some_u32 & self.some_u16 as u32;
///         Ok(())
///     }
/// }
/// ```
///
/// #### de_fn
/// * Not supported for enums. *
///
/// Defines a semantic deserialization function for a field. The function needs
/// to be specified as a string and implemented as a method attached to
/// the structure. The prototype of the function is
/// `fn(&mut self, u16) -> VersionizeResult<()>`.
///
/// If defined, the method is called if the field is skipped from
/// deserialization because it does not exist in the source version of the
/// serialized structure. Its implementation can perform any mutation of `self`
/// or return an error to stop deserialization. Intended usage is to implement
/// semantic translation or semantic validations.
///
/// Both `default_fn` and `de_fn` can be specified for a field. `default_fn` is
/// always called first and `de_fn` last.
///
/// ```ignore
/// extern crate versionize;
/// extern crate versionize_derive;
/// use versionize::{Versionize, VersionizeError, VersionizeResult};
/// use versionize_derive::Versionize;
///
/// #[derive(Clone, Versionize)]
/// struct SomeStruct {
///     some_u32: u32,
///     #[version(start = 2, ser_fn = "ser_u16", de_fn = "de_u16")]
///     some_u16: u16,
/// }
///
/// impl SomeStruct {
///     fn ser_u16(&mut self, target_version: u16) -> VersionizeResult<()> {
///         self.some_u32 = self.some_u32 & self.some_u16 as u32;
///         Ok(())
///     }
///     fn de_u16(&mut self, source_version: u16) -> VersionizeResult<()> {
///         if source_version < 2 {
///             self.some_u16 = (self.some_u32 & 0xFF) as u16;
///         }
///         Ok(())
///     }
/// }
/// ```
#[proc_macro_derive(Versionize, attributes(version))]
pub fn impl_versionize(input: TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let ident = input.ident.clone();
    let generics = input.generics.clone();

    let descriptor: Box<dyn Descriptor> = match &input.data {
        syn::Data::Struct(data_struct) => {
            Box::new(StructDescriptor::new(&data_struct, ident.clone()))
        }
        syn::Data::Enum(data_enum) => Box::new(EnumDescriptor::new(&data_enum, ident.clone())),
        syn::Data::Union(_) => {
            return (quote! {
                compile_error!("Union serialization is not supported.");
            })
            .into()
        }
    };

    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, <Self as Versionize>::type_id());
        // We will use this copy to perform semantic serialization.
        let mut copy_of_self = self.clone();
        match version {
            #versioned_serializer
            _ => panic!("Unknown {:?} version {}.", &<Self as Versionize>::type_id(), version)
        }
    };
    (quote! {
        impl Versionize for #ident #generics {
            fn serialize<W: std::io::Write>(&self, writer: &mut W, version_map: &VersionMap, app_version: u16) -> VersionizeResult<()> {
                #serializer
                Ok(())
            }

            fn deserialize<R: std::io::Read>(mut reader: &mut R, version_map: &VersionMap, app_version: u16) -> VersionizeResult<Self> {
                #deserializer
            }

            // Returns struct current version.
            fn version() -> u16 {
                #version
            }
        }
    }).into()
}