Skip to main content

diskann_record/
version.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6//! Semver-style version stamps embedded in every saved object.
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
10
11/// A semver-style schema version attached to every saved record.
12///
13/// Each [`crate::save::Save`] / [`crate::load::Load`] impl declares its
14/// `const VERSION: Version`. On load, the version recorded in the manifest is compared
15/// against the declared version to dispatch between
16/// [`Load::load`](crate::load::Load::load) and
17/// [`Load::load_legacy`](crate::load::Load::load_legacy).
18///
19/// The framework treats versions as opaque pairs and only checks them for equality;
20/// ordering / semver semantics are entirely up to the implementing type.
21///
22/// The `major.minor` format aids code readability: a reader can tell at a glance
23/// that, for example, version `1.0` is compatible with version `1.2`.
24///
25/// On the wire, a `Version` is encoded as a single string of the form
26/// `"major.minor"` (e.g. `"0.0"`).
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Version {
29    pub major: u32,
30    pub minor: u32,
31}
32
33impl Version {
34    /// Construct a [`Version`] from its two components.
35    pub const fn new(major: u32, minor: u32) -> Self {
36        Self { major, minor }
37    }
38}
39
40impl std::fmt::Display for Version {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "{}.{}", self.major, self.minor)
43    }
44}
45
46impl std::str::FromStr for Version {
47    type Err = ParseVersionError;
48
49    fn from_str(s: &str) -> Result<Self, Self::Err> {
50        let mut parts = s.split('.');
51        let major = parts.next().and_then(|s| s.parse::<u32>().ok());
52        let minor = parts.next().and_then(|s| s.parse::<u32>().ok());
53        match (major, minor, parts.next()) {
54            (Some(major), Some(minor), None) => Ok(Version { major, minor }),
55            _ => Err(ParseVersionError(s.to_owned())),
56        }
57    }
58}
59
60/// Error returned when a string cannot be parsed as a [`Version`].
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct ParseVersionError(String);
63
64impl std::fmt::Display for ParseVersionError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(
67            f,
68            "unknown version {:?}: expected two `.`-separated u32 components",
69            self.0,
70        )
71    }
72}
73
74impl std::error::Error for ParseVersionError {}
75
76#[cfg(feature = "serde")]
77impl Serialize for Version {
78    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
79        ser.collect_str(self)
80    }
81}
82
83#[cfg(feature = "serde")]
84impl<'de> Deserialize<'de> for Version {
85    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
86        struct VersionVisitor;
87
88        impl de::Visitor<'_> for VersionVisitor {
89            type Value = Version;
90
91            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
92                f.write_str("a version string of the form \"major.minor\"")
93            }
94
95            fn visit_str<E: de::Error>(self, v: &str) -> Result<Version, E> {
96                v.parse().map_err(E::custom)
97            }
98        }
99
100        de.deserialize_str(VersionVisitor)
101    }
102}
103
104#[cfg(all(test, feature = "disk"))]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn serializes_as_dotted_string() {
110        let json = serde_json::to_string(&Version::new(1, 2)).unwrap();
111        assert_eq!(json, "\"1.2\"");
112    }
113
114    #[test]
115    fn round_trips_through_json() {
116        let v = Version::new(4, 5);
117        let back: Version = serde_json::from_str(&serde_json::to_string(&v).unwrap()).unwrap();
118        assert_eq!(v, back);
119    }
120
121    #[test]
122    fn rejects_malformed_strings() {
123        for bad in ["\"1\"", "\"1.2.3\"", "\"1.x\"", "\"abc\""] {
124            serde_json::from_str::<Version>(bad)
125                .expect_err("malformed version string must be rejected");
126        }
127    }
128}