serde_version_derive 0.4.1

Versioning support for Serde
Documentation

Serde version   Build Status Latest Version

Serde version

//! Versioning support for serde. //! When software are developped and used at the same time the data formats may change from one version to another and persisting data may be produced by a specific version and loaded by another version. //! Serde version provide a versioning feature for serde for the main use cases. //! Note 1: Requires the specialization feature. Note 2: Use the derive feature to generate the DeserializeVersioned implementation //!

Goals of Serde version

//! We aim at solving the case were a type or a set of types in a deserializer's data needs to be upgraded to their latest format. This is the case when a mandatory property was added or removed, or an existing property changed. //! Note: There already is support for added optional properties in serde. (Use the default feature of serde) //! Example: Let's have a file containing these structure with those version number: A: 1, B: 1, C: 2 and the current version numbers are: A: 3, B: 2, C: 4. //! Then in latest code version, we have the former data structures versions, let's call them: Av1, Av2, Bv1, Cv1, Cv2, Cv3. //! Deserializing, whenever a structure A, B or C is ran into, then it is loaded with the appropriate format (in our case it will be Av1, Bv1 and Cv2) and then converted to A, B or C using the From trait. //!

Non goals

//! This is based on types that can be upgraded individually. Types that needs to be upgraded together is way more complex to handle and usually relies on domain specific deserializer. //! So, these data format should be handle with specific Deserialize traits implementations. //!

Unsupported Serde feature with versioning

//!

deserialize_in_place is not supported

//! Deserializing in place with versioning support is way more complicated, so we don't deal with this in this crate. //!

Not supported with deserialize_with callback

//! You must take care of the versioning in your callback //!

Versioning is only supported for structs and enums

//! There is no use case where versioning tuples and the unit type is useful. //!

Usage

//! To describe the previous versions of a type, we use the #[versions(...)] attribute along with the DeserializeVersioned trait. //! Authoring example:

// Version 1 of struct A
// It must implement Deserialize, so it can be loaded by serde
#[derive(Deserialize)]
// It must be identified by A during deserialization
#[serde(rename = "A")]
struct Av1 {
  a: u8
}
//!
// Current version of struct A
// It must implement Deserialize and DeserializeVersioned
#[derive(Deserialize, DeserializeVersioned)]
// We use the versions attribute to define the previous versions
#[versions(v(index = 1, type = "Av1"), v(index = 2, type = "A"))]
// So, Version n°1 of A is Av1, Versions n°2 (current) of A is A
struct A {
  // We moved a property
  b: u8
}
//!
// A must implement From for all previous type, so we implement From<Av1>
impl From<Av1> for A {
  fn from(v: Av1) -> Self {
    Self {
      b: v.a
    }
  }
}

//! To perform the deserialization with the versioning support, we need to do two steps:

  1. Get the VersionMap which holds the version number to use per type
  2. Call the deserialize_versioned method with the VersionMap //! Note: The id used to find the version number of a type during deserialization is the deserialization name of the type. //! Execution example:
# #![feature(specialization)]
#
# #[macro_use]
# extern crate serde_version_derive;
#
# use serde::Deserialize;
# use std::fmt::Debug;
#
# #[derive(Deserialize)]
# #[serde(rename = "A")]
# struct Av1 {
#   a: u8
# }
# #[derive(Deserialize, DeserializeVersioned, PartialEq, Debug)]
# #[versions(v(index = 1, type = "Av1"), v(index = 2, self))]
# struct A {
#   b: u8
# }
# impl From<Av1> for A {
#   fn from(v: Av1) -> Self {
#     Self {
#       b: v.a
#     }
#   }
# }
//!
#[derive(Deserialize, PartialEq, Debug)]
struct AInMap {
  a: A,
}
//!
// Use ron as data format for this example
use ron;
use serde_version::DeserializeVersioned;
//!
fn main() {
  // First get a header
  // Here, we use the version 1 of `A`
  // Note: `rust_out` is the module used for the doc script
  let versions: std::collections::HashMap<String, usize> = ron::de::from_str(r#"{ "rust_out::A": 1 }"#).unwrap();
  
  // Let's deserialize some values
  // Deserialize directly A
  let mut deserializer = ron::de::Deserializer::from_str(r#"A(a: 1)"#).unwrap();
  let value = A::deserialize_versioned(&mut deserializer, &versions).unwrap();
  assert_eq!(value, A { b: 1 });
  
  // Deserialize A contained in a struct property
  let mut deserializer = ron::de::Deserializer::from_str(r#"AInMap(a: A(a: 2))"#).unwrap();
  // Note: All types implementing `Deserialize` will also implement `DeserializeVersioned`
  let value = AInMap::deserialize_versioned(&mut deserializer, &versions).unwrap();
  assert_eq!(value.a, A { b: 2});
}

//!

VersionedDeserializer

//! Under the hood, deserialize_version wraps the provided deserializer with the VersionedDeserializer to support the versioning. //!

Versioned groups

//! A version group is a set of types with their associated version. It is often easier to use a version number for multiple types together. //! You can refer to a version group by a VersionGroupURI, this is an identifier used to select the appropriate VersionMap to use. //! The VersionGroupResolver trait is then used to get the VersionMap associated to a VersionGroupURI. //! You can easily create version group resolver, uris and maps with the provided macros. //! Example:

version_group_resolver_static! {
    pub VERSIONS = {
        ("version_group.example" , "1.0.0") => { A => 1, B => 1, },
        ("version_group.example" , "1.1.0") => { A => 3, B => 1, },
        ("version_group.example" , "1.2.0") => { A => 4, B => 2, },
    }
}
//!
// Define an enum to have an easy way to get the version uris
version_group_enum! {
    #[derive(Deserialize)]
    enum Versions {
        V1 as "v1" => "version_group.example:1.0.0",
        V2 as "v2" => "version_group.example:1.1.0",
        V3 as "v3" => "version_group.example:1.2.0",
    }
}
//!
use common::deserialize_test;
//!
// V1
deserialize_test(
    "A(a: 8)",
    A { c: 8 },
    VERSIONS.resolve(Versions::V1.into()).unwrap(),
);
deserialize_test(
    "B(a: 8)",
    B { c: 8 },
    VERSIONS.resolve(Versions::V1.into()).unwrap(),
);
deserialize_test(
    "ContainsBoth(a: A(a: 9), b: B(a: 10))",
    ContainsBoth {
        a: A { c: 9 },
        b: B { c: 8 },
    },
    VERSIONS.resolve(Versions::V1.into()).unwrap(),
);

//! Use the example versioned_groups to see it in action.