Skip to main content

vbare/
versioned.rs

1//! Versioned framing: a 2-byte little-endian version header followed by the message body.
2
3use crate::{
4    body::Body,
5    error::{DecodeError, DecodeErrorKind, EncodeError},
6    message::{Encode, Message, View},
7    sink::Sink,
8};
9use bytes::{Bytes, BytesMut};
10
11/// The length of the little-endian `u16` version header.
12const HEADER_LEN: usize = 2;
13
14/// A converter between adjacent versions of a versioned enum.
15pub type Converter<T> = fn(T) -> anyhow::Result<T>;
16
17/// A hand-written enum with one variant per schema version, plus converters between adjacent
18/// versions. The trait provides decoding, viewing and encoding at any supported version.
19///
20/// Versions are numbered from 1. Payloads at [`LATEST`](Versioned::LATEST) are viewed and
21/// decoded directly from the input; older payloads are decoded, upgraded, and re-encoded at the
22/// latest version, so only legacy data pays for conversion.
23///
24/// The header is the version as a little-endian `u16`, byte-identical to vbare 0.1's
25/// `serialize_with_embedded_version`.
26///
27/// The latest value is usually a generated message. It can be any [`Body`], such as a domain type
28/// stored as a generated message or an `Option<i64>`; only [`open`](Versioned::open) and
29/// [`open_body`](Versioned::open_body), which return a view, need a message.
30pub trait Versioned: Sized + 'static {
31    type Latest: Body;
32
33    /// The newest version, which [`Latest`](Versioned::Latest) encodes.
34    const LATEST: u16;
35
36    /// `UPGRADES[i]` converts version `i + 1` to version `i + 2`.
37    const UPGRADES: &'static [Converter<Self>] = &[];
38
39    /// `DOWNGRADES[i]` converts version `LATEST - i` to version `LATEST - i - 1`.
40    const DOWNGRADES: &'static [Converter<Self>] = &[];
41
42    fn wrap_latest(latest: Self::Latest) -> Self;
43
44    fn unwrap_latest(self) -> anyhow::Result<Self::Latest>;
45
46    /// Decodes a body written at `version`, which is never [`LATEST`](Versioned::LATEST).
47    fn decode_version(version: u16, body: Bytes) -> Result<Self, DecodeError>;
48
49    /// Encodes the variant this value holds. `version` is the version being written.
50    fn encode_version<S: Sink>(&self, version: u16, out: &mut S) -> Result<(), EncodeError>;
51
52    /// Decodes a body without a header, written at `version`.
53    fn decode_body(body: Bytes, version: u16) -> Result<Self::Latest, DecodeError> {
54        if version == Self::LATEST {
55            return Self::Latest::from_body(body);
56        }
57        check_decode_version::<Self>(version)?;
58        let mut value = Self::decode_version(version, body)?;
59        let skip = usize::from(version) - 1;
60        for upgrade in Self::UPGRADES.iter().skip(skip) {
61            value = upgrade(value).map_err(DecodeError::conversion)?;
62        }
63        value.unwrap_latest().map_err(DecodeError::conversion)
64    }
65
66    /// Views a body without a header, written at `version`. Latest-version bodies are viewed in
67    /// place; older bodies are converted and re-encoded first.
68    fn open_body(
69        body: Bytes,
70        version: u16,
71    ) -> Result<View<<Self::Latest as Message>::Wire>, DecodeError>
72    where
73        Self::Latest: Message,
74    {
75        if version == Self::LATEST {
76            return Self::Latest::view(body);
77        }
78        let latest = Self::decode_body(body, version)?;
79        View::new(latest.encode_to_bytes())
80    }
81
82    /// Encodes `value` at `version` without a header, converting it down if needed.
83    fn encode_body<S: Sink>(
84        value: &Self::Latest,
85        version: u16,
86        out: &mut S,
87    ) -> Result<(), EncodeError> {
88        if version == Self::LATEST {
89            value.put_body(out);
90            return Ok(());
91        }
92        downgrade::<Self>(value, version)?.encode_version(version, out)
93    }
94
95    /// Decodes a payload with a version header to the latest version.
96    fn decode(bytes: Bytes) -> Result<Self::Latest, DecodeError> {
97        let (version, body) = split_header(bytes)?;
98        Self::decode_body(body, version)
99    }
100
101    /// Views a payload with a version header. A latest-version payload is viewed without copying.
102    fn open(bytes: Bytes) -> Result<View<<Self::Latest as Message>::Wire>, DecodeError>
103    where
104        Self::Latest: Message,
105    {
106        let (version, body) = split_header(bytes)?;
107        Self::open_body(body, version)
108    }
109
110    /// Writes the version header and `value` encoded at `version` into `out`.
111    fn seal<S: Sink>(value: &Self::Latest, version: u16, out: &mut S) -> Result<(), EncodeError> {
112        if version == Self::LATEST {
113            out.put_u16_le(version);
114            value.put_body(out);
115            return Ok(());
116        }
117        let converted = downgrade::<Self>(value, version)?;
118        out.put_u16_le(version);
119        converted.encode_version(version, out)
120    }
121
122    /// The exact number of bytes [`seal`](Versioned::seal) writes for `value` at the latest
123    /// version: the header plus the body. An older version converts the value first, so its length
124    /// is only known once it is encoded.
125    fn sealed_len(value: &Self::Latest) -> usize {
126        HEADER_LEN + value.body_len()
127    }
128
129    /// [`seal`](Versioned::seal) into a new `Vec`.
130    fn seal_to_vec(value: &Self::Latest, version: u16) -> Result<Vec<u8>, EncodeError> {
131        let mut out = Vec::with_capacity(Self::sealed_len(value));
132        Self::seal(value, version, &mut out)?;
133        Ok(out)
134    }
135
136    /// [`seal`](Versioned::seal) into a new `Bytes`, allocated once at the exact length for the
137    /// latest version.
138    fn seal_to_bytes(value: &Self::Latest, version: u16) -> Result<Bytes, EncodeError> {
139        let mut out = BytesMut::with_capacity(Self::sealed_len(value));
140        Self::seal(value, version, &mut out)?;
141        Ok(out.freeze())
142    }
143
144    /// Seals `value` at `version` into the front of `out` and returns the number of bytes
145    /// written. Fails with [`EncodeError::BufferTooSmall`] instead of panicking when `out` is too
146    /// short, and then writes nothing.
147    ///
148    /// At the latest version the header and body go straight into `out` after one length check.
149    /// An older version is converted and encoded into a temporary buffer first, because its
150    /// length is not known before encoding, so only legacy writes pay for a copy.
151    fn seal_to_slice(
152        value: &Self::Latest,
153        version: u16,
154        out: &mut [u8],
155    ) -> Result<usize, EncodeError> {
156        if version != Self::LATEST {
157            let sealed = Self::seal_to_vec(value, version)?;
158            check_capacity(sealed.len(), out.len())?;
159            out[..sealed.len()].copy_from_slice(&sealed);
160            return Ok(sealed.len());
161        }
162
163        let needed = Self::sealed_len(value);
164        check_capacity(needed, out.len())?;
165        let mut cursor = &mut out[..needed];
166        Self::seal(value, version, &mut cursor)?;
167        Ok(needed)
168    }
169
170    /// [`decode`](Versioned::decode) from a slice, under vbare 0.1's name. Copies `payload` once;
171    /// callers that hold `Bytes` should call `decode` instead.
172    fn deserialize_with_embedded_version(payload: &[u8]) -> anyhow::Result<Self::Latest> {
173        Self::decode(Bytes::copy_from_slice(payload)).map_err(decode_error)
174    }
175
176    /// [`seal_to_vec`](Versioned::seal_to_vec) under vbare 0.1's name. Takes the value by
177    /// reference, so callers no longer clone it into [`wrap_latest`](Versioned::wrap_latest).
178    fn serialize_with_embedded_version(
179        value: &Self::Latest,
180        version: u16,
181    ) -> anyhow::Result<Vec<u8>> {
182        Self::seal_to_vec(value, version).map_err(encode_error)
183    }
184
185    /// [`decode_body`](Versioned::decode_body) from a slice, under vbare 0.1's name. Copies
186    /// `payload` once.
187    fn deserialize(payload: &[u8], version: u16) -> anyhow::Result<Self::Latest> {
188        Self::decode_body(Bytes::copy_from_slice(payload), version).map_err(decode_error)
189    }
190
191    /// [`encode_body`](Versioned::encode_body) into a new `Vec`, under vbare 0.1's name.
192    fn serialize(value: &Self::Latest, version: u16) -> anyhow::Result<Vec<u8>> {
193        let mut out = Vec::with_capacity(value.body_len());
194        Self::encode_body(value, version, &mut out).map_err(encode_error)?;
195        Ok(out)
196    }
197}
198
199/// Returns a converter's own error unchanged, as vbare 0.1 did, so callers can still downcast it.
200fn decode_error(error: DecodeError) -> anyhow::Error {
201    match error.into_conversion() {
202        Ok(conversion) => conversion,
203        Err(error) => error.into(),
204    }
205}
206
207/// See [`decode_error`].
208fn encode_error(error: EncodeError) -> anyhow::Error {
209    match error {
210        EncodeError::Conversion(conversion) => conversion,
211        EncodeError::BufferTooSmall { .. } | EncodeError::UnsupportedVersion(_) => error.into(),
212    }
213}
214
215fn check_capacity(needed: usize, available: usize) -> Result<(), EncodeError> {
216    if needed > available {
217        return Err(EncodeError::BufferTooSmall { needed, available });
218    }
219    Ok(())
220}
221
222fn split_header(mut bytes: Bytes) -> Result<(u16, Bytes), DecodeError> {
223    if bytes.len() < HEADER_LEN {
224        return Err(DecodeError::with_detail(
225            DecodeErrorKind::Truncated,
226            bytes.len(),
227            "missing version header",
228        ));
229    }
230    let body = bytes.split_off(HEADER_LEN);
231    let version = u16::from_le_bytes([bytes[0], bytes[1]]);
232    Ok((version, body))
233}
234
235fn check_decode_version<T: Versioned>(version: u16) -> Result<(), DecodeError> {
236    if version == 0 || version > T::LATEST {
237        return Err(DecodeError::with_detail(
238            DecodeErrorKind::UnsupportedVersion,
239            0,
240            format!("version {version}, latest is {}", T::LATEST),
241        ));
242    }
243    Ok(())
244}
245
246/// Converts `value` from the latest version down to `version`.
247fn downgrade<T: Versioned>(value: &T::Latest, version: u16) -> Result<T, EncodeError> {
248    if version == 0 || version > T::LATEST {
249        return Err(EncodeError::UnsupportedVersion(version));
250    }
251    let steps = usize::from(T::LATEST - version);
252    let mut converted = T::wrap_latest(value.clone());
253    for downgrade in T::DOWNGRADES.iter().take(steps) {
254        converted = downgrade(converted).map_err(EncodeError::Conversion)?;
255    }
256    Ok(converted)
257}