//!
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.
//!
See the guide here.
//!
Note 1: Requires the specialization feature.
Note 2: Use the derive
feature to generate the DeserializeVersioned
implementation
//!
//!
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.
//!
//!
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.
//!
//!
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. //!
deserialize_with
callback//! You must take care of the versioning in your callback //!
//! There is no use case where versioning tuples and the unit type is useful. //!
//!
To describe the previous versions of a type, we use the #[versions(...)]
attribute along with
the DeserializeVersioned
trait.
//!
Authoring example:
```rust
// Version 1 of struct A
// It must implement Deserialize, so it can be loaded by serde
// It must be identified by A during deserialization
struct Av1 { a: u8 } //! // Current version of struct A // It must implement Deserialize and DeserializeVersioned
// We use the versions attribute to define the previous versions
// 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
//!
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
1. 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:
rust
#
#
#
//!
struct AInMap {
a: A,
}
//!
// Use ron as data format for this example
use ron;
use serdeversion::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
// Let's deserialize some values // Deserialize directly A let mut deserializer = ron::de::Deserializer::fromstr(r#"A(a: 1)"#).unwrap(); let value = A::deserializeversioned(&mut deserializer, &versions).unwrap(); assert_eq!(value, A { b: 1 });
// Deserialize A contained in a struct property
let mut deserializer = ron::de::Deserializer::fromstr(r#"AInMap(a: A(a: 2))"#).unwrap();
// Note: All types implementing Deserialize
will also implement DeserializeVersioned
let value = AInMap::deserializeversioned(&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.
//!
//!
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:
rust
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.