Skip to main content

astarte_interfaces/interface/
version.rs

1// This file is part of Astarte.
2//
3// Copyright 2025 SECO Mind Srl
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// SPDX-License-Identifier: Apache-2.0
18
19//! Version of an Interface
20//!
21//! An interface has a major and minor version to specify the compatibility and changes.
22
23use std::fmt::Display;
24
25/// Error returned by the [`InterfaceVersion`].
26#[derive(Debug, thiserror::Error)]
27pub enum VersionError {
28    /// The version cannot be negative
29    #[error("the version cannot be negative: {major}.{minor}")]
30    Negative {
31        /// The provided major version
32        major: i32,
33        /// The provided minor version
34        minor: i32,
35    },
36    /// The version of an interface cannot be 0.0
37    #[error("the version of an interface cannot be 0.0")]
38    Zero,
39}
40
41/// Version of an interface.
42///
43/// This structs validates to follow the interface versioning rules:
44///
45/// - The major or minor version must an [`i32`]
46/// - The major or minor version must be positive
47/// - The minor version must be grater than `0>` if the major is `=0`
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct InterfaceVersion {
50    version_major: i32,
51    version_minor: i32,
52}
53
54impl InterfaceVersion {
55    /// Validate the provided interface version
56    pub fn try_new(version_major: i32, version_minor: i32) -> Result<Self, VersionError> {
57        if version_major.is_negative() || version_minor.is_negative() {
58            return Err(VersionError::Negative {
59                major: version_major,
60                minor: version_minor,
61            });
62        }
63
64        if version_major == 0 && version_minor == 0 {
65            return Err(VersionError::Zero);
66        }
67
68        // Checked negativity above
69        Ok(Self {
70            version_major,
71            version_minor,
72        })
73    }
74
75    /// Returns the major version of the interface
76    #[must_use]
77    pub fn version_major(&self) -> i32 {
78        self.version_major
79    }
80
81    /// Returns the minor version of the interface
82    #[must_use]
83    pub fn version_minor(&self) -> i32 {
84        self.version_minor
85    }
86}
87
88impl Display for InterfaceVersion {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        write!(f, "{}.{}", self.version_major, self.version_minor)
91    }
92}
93
94impl From<&InterfaceVersion> for (i32, i32) {
95    fn from(value: &InterfaceVersion) -> Self {
96        (value.version_major, value.version_minor)
97    }
98}
99
100impl From<InterfaceVersion> for (i32, i32) {
101    fn from(value: InterfaceVersion) -> Self {
102        (value.version_major, value.version_minor)
103    }
104}
105
106impl TryFrom<(i32, i32)> for InterfaceVersion {
107    type Error = VersionError;
108
109    fn try_from((major, minor): (i32, i32)) -> Result<Self, Self::Error> {
110        Self::try_new(major, minor)
111    }
112}
113
114impl Default for InterfaceVersion {
115    fn default() -> Self {
116        Self {
117            version_major: 0,
118            version_minor: 1,
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use pretty_assertions::assert_eq;
126
127    use super::*;
128
129    #[test]
130    fn check_into() {
131        let ver = InterfaceVersion::try_from((0, 1)).unwrap();
132        let ver: (i32, i32) = ver.into();
133
134        assert_eq!(ver, (0, 1));
135    }
136
137    #[test]
138    fn check_negative() {
139        InterfaceVersion::try_from((0, -1)).unwrap_err();
140        InterfaceVersion::try_from((-1, 0)).unwrap_err();
141        InterfaceVersion::try_from((-1, -1)).unwrap_err();
142    }
143
144    #[test]
145    fn check_zero() {
146        InterfaceVersion::try_from((0, 0)).unwrap_err();
147    }
148
149    #[test]
150    fn check_default() {
151        assert_eq!(
152            InterfaceVersion::default(),
153            InterfaceVersion {
154                version_major: 0,
155                version_minor: 1
156            }
157        );
158    }
159
160    #[test]
161    fn check_getters() {
162        let ver = InterfaceVersion::default();
163
164        assert_eq!(ver.version_major(), 0);
165        assert_eq!(ver.version_minor(), 1);
166    }
167
168    #[test]
169    fn check_display() {
170        let ver = InterfaceVersion::try_from((0, 1)).unwrap().to_string();
171        assert_eq!(ver, "0.1");
172    }
173}