use std::{cmp::Ordering, fmt::Display};
use super::Interface;
#[non_exhaustive]
#[derive(thiserror::Error, Debug, Clone, Copy)]
pub enum VersionChangeError {
#[error("the major version decreased: {0}")]
MajorDecreased(VersionChange),
#[error("the minor version decreased: {0}")]
MinorDecreased(VersionChange),
#[error("the version did not change: {0}")]
SameVersion(VersionChange),
}
#[derive(Debug, Clone, Copy)]
pub struct VersionChange {
next_major: i32,
next_minor: i32,
prev_major: i32,
prev_minor: i32,
}
impl VersionChange {
pub fn try_new(next: &Interface, prev: &Interface) -> Result<Self, VersionChangeError> {
let change = Self {
next_major: next.version_major(),
next_minor: next.version_minor(),
prev_major: prev.version_major(),
prev_minor: prev.version_minor(),
};
change.validate()
}
#[must_use]
pub fn previous(&self) -> (i32, i32) {
(self.prev_major, self.prev_minor)
}
#[must_use]
pub fn next(&self) -> (i32, i32) {
(self.next_major, self.next_minor)
}
pub fn validate(self) -> Result<Self, VersionChangeError> {
let major = self.next_major.cmp(&self.prev_major);
let minor = self.next_minor.cmp(&self.prev_minor);
match (major, minor) {
(Ordering::Less, _) => Err(VersionChangeError::MajorDecreased(self)),
(Ordering::Equal, Ordering::Less) => Err(VersionChangeError::MinorDecreased(self)),
(Ordering::Equal, Ordering::Equal) => Err(VersionChangeError::SameVersion(self)),
_ => Ok(self),
}
}
}
impl Display for VersionChange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}.{} -> {}.{}",
self.prev_major, self.prev_minor, self.next_major, self.next_minor
)
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use crate::Interface;
#[test]
fn version_change() {
let prev = make_interface(1, 2);
let next = make_interface(2, 2);
let change = super::VersionChange::try_new(&next, &prev);
assert!(change.is_ok());
let change = change.unwrap();
assert_eq!(change.previous(), (1, 2));
assert_eq!(change.next(), (2, 2));
assert_eq!(change.to_string(), "1.2 -> 2.2");
}
#[test]
fn validation_test() {
let interface_json = r#"
{
"interface_name": "org.astarte-platform.genericsensors.Values",
"version_major": 0,
"version_minor": 0,
"type": "datastream",
"ownership": "device",
"description": "Interface description",
"doc": "Interface doc",
"mappings": [
{
"endpoint": "/%{sensor_id}/value",
"type": "double",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
},
{
"endpoint": "/%{sensor_id}/otherValue",
"type": "longinteger",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
}
]
}"#;
let deser_interface = Interface::from_str(interface_json);
assert!(deser_interface.is_err());
}
#[test]
fn validate_same_interface() {
let prev_interface = Interface::from_str(
r#"
{
"interface_name": "org.astarte-platform.genericsensors.Values",
"version_major": 1,
"version_minor": 0,
"type": "datastream",
"ownership": "device",
"description": "Interface description",
"doc": "Interface doc",
"mappings": [
{
"endpoint": "/%{sensor_id}/value",
"type": "double",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
},
{
"endpoint": "/%{sensor_id}/otherValue",
"type": "longinteger",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
}
]
}"#,
)
.unwrap();
let new_interface = Interface::from_str(
r#"
{
"interface_name": "org.astarte-platform.genericsensors.Values",
"version_major": 1,
"version_minor": 0,
"type": "datastream",
"ownership": "device",
"description": "Interface description",
"doc": "Interface doc",
"mappings": [
{
"endpoint": "/%{sensor_id}/value",
"type": "double",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
},
{
"endpoint": "/%{sensor_id}/otherValue",
"type": "longinteger",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
}
]
}"#,
)
.unwrap();
assert!(new_interface.validate_with(&prev_interface).is_ok());
}
#[test]
fn validate_version() {
let interfaces = [
(make_interface(1, 0), make_interface(1, 1), true),
(make_interface(2, 1), make_interface(1, 1), false),
(make_interface(1, 2), make_interface(1, 1), false),
(make_interface(1, 1), make_interface(1, 1), true),
];
for (prev, new, expected) in interfaces {
let res = new.validate_with(&prev);
assert_eq!(
res.is_ok(),
expected,
"expected to {}: {:?}",
if expected { "pass" } else { "fail" },
res
);
}
}
fn make_interface(major: i32, minor: i32) -> Interface {
Interface::from_str(&format!(
r#"{{
"interface_name": "org.astarte-platform.genericsensors.Values",
"version_major": {major},
"version_minor": {minor},
"type": "datastream",
"ownership": "device",
"description": "Interface description",
"doc": "Interface doc",
"mappings": [{{
"endpoint": "/value",
"type": "double",
"description": "Mapping description",
"doc": "Mapping doc"
}}]
}}"#
))
.unwrap()
}
}