astarte_interfaces/interface/
validation.rs1use std::{cmp::Ordering, fmt::Display};
22
23use super::Interface;
24
25#[non_exhaustive]
27#[derive(thiserror::Error, Debug, Clone, Copy)]
28pub enum VersionChangeError {
29 #[error("the major version decreased: {0}")]
31 MajorDecreased(VersionChange),
32 #[error("the minor version decreased: {0}")]
34 MinorDecreased(VersionChange),
35 #[error("the version did not change: {0}")]
37 SameVersion(VersionChange),
38}
39
40#[derive(Debug, Clone, Copy)]
48pub struct VersionChange {
49 next_major: i32,
50 next_minor: i32,
51 prev_major: i32,
52 prev_minor: i32,
53}
54
55impl VersionChange {
56 pub fn try_new(next: &Interface, prev: &Interface) -> Result<Self, VersionChangeError> {
58 let change = Self {
59 next_major: next.version_major(),
60 next_minor: next.version_minor(),
61 prev_major: prev.version_major(),
62 prev_minor: prev.version_minor(),
63 };
64
65 change.validate()
66 }
67
68 #[must_use]
70 pub fn previous(&self) -> (i32, i32) {
71 (self.prev_major, self.prev_minor)
72 }
73
74 #[must_use]
76 pub fn next(&self) -> (i32, i32) {
77 (self.next_major, self.next_minor)
78 }
79
80 pub fn validate(self) -> Result<Self, VersionChangeError> {
84 let major = self.next_major.cmp(&self.prev_major);
85 let minor = self.next_minor.cmp(&self.prev_minor);
86
87 match (major, minor) {
88 (Ordering::Less, _) => Err(VersionChangeError::MajorDecreased(self)),
89 (Ordering::Equal, Ordering::Less) => Err(VersionChangeError::MinorDecreased(self)),
90 (Ordering::Equal, Ordering::Equal) => Err(VersionChangeError::SameVersion(self)),
91 _ => Ok(self),
92 }
93 }
94}
95
96impl Display for VersionChange {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 write!(
99 f,
100 "{}.{} -> {}.{}",
101 self.prev_major, self.prev_minor, self.next_major, self.next_minor
102 )
103 }
104}
105
106#[cfg(test)]
107mod test {
108 use std::str::FromStr;
109
110 use crate::Interface;
111
112 #[test]
113 fn version_change() {
114 let prev = make_interface(1, 2);
115 let next = make_interface(2, 2);
116
117 let change = super::VersionChange::try_new(&next, &prev);
118
119 assert!(change.is_ok());
120
121 let change = change.unwrap();
122
123 assert_eq!(change.previous(), (1, 2));
124 assert_eq!(change.next(), (2, 2));
125
126 assert_eq!(change.to_string(), "1.2 -> 2.2");
127 }
128
129 #[test]
130 fn validation_test() {
131 let interface_json = r#"
133 {
134 "interface_name": "org.astarte-platform.genericsensors.Values",
135 "version_major": 0,
136 "version_minor": 0,
137 "type": "datastream",
138 "ownership": "device",
139 "description": "Interface description",
140 "doc": "Interface doc",
141 "mappings": [
142 {
143 "endpoint": "/%{sensor_id}/value",
144 "type": "double",
145 "explicit_timestamp": true,
146 "description": "Mapping description",
147 "doc": "Mapping doc"
148 },
149 {
150 "endpoint": "/%{sensor_id}/otherValue",
151 "type": "longinteger",
152 "explicit_timestamp": true,
153 "description": "Mapping description",
154 "doc": "Mapping doc"
155 }
156 ]
157 }"#;
158
159 let deser_interface = Interface::from_str(interface_json);
160
161 assert!(deser_interface.is_err());
162 }
163
164 #[test]
165 fn validate_same_interface() {
166 let prev_interface = Interface::from_str(
167 r#"
168 {
169 "interface_name": "org.astarte-platform.genericsensors.Values",
170 "version_major": 1,
171 "version_minor": 0,
172 "type": "datastream",
173 "ownership": "device",
174 "description": "Interface description",
175 "doc": "Interface doc",
176 "mappings": [
177 {
178 "endpoint": "/%{sensor_id}/value",
179 "type": "double",
180 "explicit_timestamp": true,
181 "description": "Mapping description",
182 "doc": "Mapping doc"
183 },
184 {
185 "endpoint": "/%{sensor_id}/otherValue",
186 "type": "longinteger",
187 "explicit_timestamp": true,
188 "description": "Mapping description",
189 "doc": "Mapping doc"
190 }
191 ]
192 }"#,
193 )
194 .unwrap();
195
196 let new_interface = Interface::from_str(
197 r#"
198 {
199 "interface_name": "org.astarte-platform.genericsensors.Values",
200 "version_major": 1,
201 "version_minor": 0,
202 "type": "datastream",
203 "ownership": "device",
204 "description": "Interface description",
205 "doc": "Interface doc",
206 "mappings": [
207 {
208 "endpoint": "/%{sensor_id}/value",
209 "type": "double",
210 "explicit_timestamp": true,
211 "description": "Mapping description",
212 "doc": "Mapping doc"
213 },
214 {
215 "endpoint": "/%{sensor_id}/otherValue",
216 "type": "longinteger",
217 "explicit_timestamp": true,
218 "description": "Mapping description",
219 "doc": "Mapping doc"
220 }
221 ]
222 }"#,
223 )
224 .unwrap();
225
226 assert!(new_interface.validate_with(&prev_interface).is_ok());
227 }
228
229 #[test]
230 fn validate_version() {
231 let interfaces = [
232 (make_interface(1, 0), make_interface(1, 1), true),
233 (make_interface(2, 1), make_interface(1, 1), false),
234 (make_interface(1, 2), make_interface(1, 1), false),
235 (make_interface(1, 1), make_interface(1, 1), true),
237 ];
238
239 for (prev, new, expected) in interfaces {
240 let res = new.validate_with(&prev);
241
242 assert_eq!(
243 res.is_ok(),
244 expected,
245 "expected to {}: {:?}",
246 if expected { "pass" } else { "fail" },
247 res
248 );
249 }
250 }
251
252 fn make_interface(major: i32, minor: i32) -> Interface {
253 Interface::from_str(&format!(
254 r#"{{
255 "interface_name": "org.astarte-platform.genericsensors.Values",
256 "version_major": {major},
257 "version_minor": {minor},
258 "type": "datastream",
259 "ownership": "device",
260 "description": "Interface description",
261 "doc": "Interface doc",
262 "mappings": [{{
263 "endpoint": "/value",
264 "type": "double",
265 "description": "Mapping description",
266 "doc": "Mapping doc"
267 }}]
268 }}"#
269 ))
270 .unwrap()
271 }
272}