use serde::Deserialize;
use super::ManifestError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionHeader {
pub version: semver::Version,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Compatibility {
Supported,
RequiresNewer { found: semver::Version },
TooOld { found: semver::Version },
}
pub fn read_version_header(content: &str, field: &str) -> Result<VersionHeader, ManifestError> {
#[derive(Deserialize)]
struct Envelope {
#[serde(flatten)]
fields: serde_json::Map<String, serde_json::Value>,
}
let envelope = serde_json::from_str::<Envelope>(content)
.map_err(|err| ManifestError::Invalid(format!("failed to parse document: {err}")))?;
let Some(raw) = envelope.fields.get(field) else {
return Err(ManifestError::MissingVersion(field.to_string()));
};
let Some(raw) = raw.as_str() else {
return Err(ManifestError::MissingVersion(field.to_string()));
};
let version = raw
.parse::<semver::Version>()
.map_err(|_| ManifestError::MissingVersion(field.to_string()))?;
Ok(VersionHeader { version })
}
pub fn classify(found: &semver::Version, supported_major: u64) -> Compatibility {
match found.major.cmp(&supported_major) {
std::cmp::Ordering::Equal => Compatibility::Supported,
std::cmp::Ordering::Greater => Compatibility::RequiresNewer { found: found.clone() },
std::cmp::Ordering::Less => Compatibility::TooOld { found: found.clone() },
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_parses_without_requiring_other_fields() {
let h = read_version_header(r#"{"manifest_version":"3.1.0"}"#, "manifest_version").unwrap();
assert_eq!(h.version, semver::Version::new(3, 1, 0));
let h = read_version_header(
r#"{"manifest_version":"2.0.0","channels":"not-an-array","future":{"a":1}}"#,
"manifest_version",
)
.unwrap();
assert_eq!(h.version, semver::Version::new(2, 0, 0));
}
#[test]
fn header_reads_the_named_field_so_one_impl_serves_both_documents() {
let h = read_version_header(r#"{"state_version":"1.0.0"}"#, "state_version").unwrap();
assert_eq!(h.version, semver::Version::new(1, 0, 0));
}
#[test]
fn classify_uses_major_only() {
assert_eq!(classify(&semver::Version::new(2, 0, 0), 2), Compatibility::Supported);
assert_eq!(classify(&semver::Version::new(2, 9, 3), 2), Compatibility::Supported);
assert!(matches!(
classify(&semver::Version::new(3, 0, 0), 2),
Compatibility::RequiresNewer { .. }
));
assert!(matches!(
classify(&semver::Version::new(1, 0, 1), 2),
Compatibility::TooOld { .. }
));
}
#[test]
fn missing_or_malformed_version_field_is_an_error() {
for bad in [
r#"{"date":1}"#, r#"{"manifest_version":2}"#, r#"{"manifest_version":"not-semver"}"#, ] {
assert!(
matches!(
read_version_header(bad, "manifest_version"),
Err(ManifestError::MissingVersion(_))
),
"should reject {bad}"
);
}
}
#[test]
fn malformed_json_is_reported_as_such() {
assert!(matches!(
read_version_header("{not json", "manifest_version"),
Err(ManifestError::Invalid(_))
));
}
}