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
//! ## Versioned data structures in Rust
//!
//! **This crate is still under development.**
//!
//! The goal of this crate is to make versioned data structures easy. For example,
//! imagine we start out with this struct:
//! ```
//! struct FooV1 {
//!     val: u32,
//! }
//! ```
//! If we serialize data to files in this format, but later discover that we want
//! to make a change:
//! ```
//! struct FooV2 {
//!     val: u64,
//! }
//! ```
//! ... then we have a bunch of work to do, if we want to support our previous
//! files. We may need to increment a version number in the file header, and
//! possibly go through all the different places where `FooV1` was used, and decide
//! whether to upgrade it to `FooV2`. Any place a `FooV1` was used, we need to keep
//! that code around, or risk breaking compatibilty.
//!
//! This crate adds traits that allow us to track the version of each struct and
//! derive traits and methods to allow upgrading any struct dynamically to the
//! latest version. This means that most code only ever needs to interact with the
//! latest version, while still retaining the ability to read older files.
//!
//! To make this work, structs must follow a particular pattern:
//! - Versioned structs must follow the naming
//!   convention `Name` + `V` + `{integer}`, i.e. `FooV1` or `BarV42`.
//! - Versions must start at 1, and be contiguous.
//! - There must be a type alias `type Foo = FooV3` that points to the latest
//!   version.
//! - For each pair of versions, `N` and `N+1`, the trait `FromVersion` must be
//!   implemented. For example:
//! ```
//! # use aversion::{FromVersion, Versioned};
//! # #[derive(Versioned)]
//! # struct FooV1 { val: u32 }
//! # #[derive(Versioned)]
//! # struct FooV2 { val: u64 }
//! # type Foo = FooV2;
//!
//! impl FromVersion<FooV1> for FooV2 {
//!     fn from_version(v1: FooV1) -> Self {
//!         FooV2 { val: v1.val.into() }
//!     }
//! }
//! ```
//!
//! This crate is still new, and these rules may evolve in the future.
//!
//! ### Deserialization
//!
//! We want to be able to deserialize data structures without knowing ahead of
//! time what version is stored.
//!
//! To do this, we use the `DataSource` trait, to specify our serialization
//! format, header format, and error types.
//!
//! Once the `UpgradeLatest` trait is implemented (there is a derive macro for
//! this), we can quickly deserialize any version of our data structure, e.g.
//! ```text
//! // Define a data source
//! let src = CborData::new(...);
//! // Read a the next header + message, and upgrade to the latest version
//! let msg: Foo = data_src.expect_message()?;
//! ```
//! Note that `msg` in this example is always the latest version of the `Foo`
//! struct, regardless of which version was actually stored. As long as the
//! `FromVersion` code is correct, the rest of the program never needs to be aware
//! of which version was read from the file.
//!
//! ### Message Groups
//!
//! We can extend this logic to groups of different messages, to automatically
//! build a dispatch function. For example, if we define a collection of messages:
//! ```
//! # struct Foo;
//! # struct Bar;
//! enum MyProtocol {
//!     Foo(Foo),
//!     Bar(Bar),
//! }
//! ```
//!
//! We can derive the trait `GroupDeserialize` that can automatically deserialize
//! any of the messages in `MyProtocol`:
//! ```ignore
//! let incoming_message = MyProtocol::read_message(&mut my_data_source)?;
//! match incoming_message {
//!     Foo(f) => {
//!         // handle the received Foo message
//!     }
//!     Bar(b) => {
//!         // handle the received Bar message
//!     }
//! }
//! ```
//! Similar to the previous example, the header will tell us which message
//! was sent (i.e. `Foo` or `Bar`), along with the version of that struct (`FooV1`
//! or `FooV2`) and `read_message` deserializes the correct version of the struct,
//! upgrades it to the latest version, and returns it as a `MyProtocol`
//! enum, for the caller to handle.

#![warn(missing_docs)]
#![forbid(unsafe_code)]
#![warn(clippy::cast_possible_truncation)]

pub mod group;
mod id;
pub mod util;
mod versioned;

#[doc(inline)]
pub use crate::versioned::{FromVersion, IntoVersion, Versioned};

#[doc(inline)]
pub use crate::group::GroupDeserialize;

#[doc(inline)]
pub use aversion_macros::{GroupDeserialize, UpgradeLatest, Versioned};

/// Implement `MessageId` for a bunch of types at once.
///
/// The `assign_message_ids!` macro uses the following syntax:
/// ```text
/// assign_message_ids! {
///     Foo: 100,
///     Bar: 101,
///     Baz: 109,
/// }
/// ```
/// This is equivalent to writing the following [`MessageId`] implementations by hand:
/// ```
/// # use aversion::{MessageId, Versioned};
/// # #[derive(Versioned)]
/// # struct FooV1;
/// # type Foo = FooV1;
/// # #[derive(Versioned)]
/// # struct BarV1;
/// # type Bar = BarV1;
/// # #[derive(Versioned)]
/// # struct BazV1;
/// # type Baz = BazV1;
///
/// impl MessageId for Foo {
///    const MSG_ID: u16 = 100;
/// }
/// impl MessageId for Bar {
///    const MSG_ID: u16 = 101;
/// }
/// impl MessageId for Baz {
///    const MSG_ID: u16 = 109;
/// }
/// ```
///
#[doc(inline)]
pub use aversion_macros::assign_message_ids;

#[doc(inline)]
pub use id::MessageId;