versa_semval 0.10.0

Cross-platform module for semantic validation of Versa data
Documentation
use crate::model::ViolationDetails;

const EARLIEST_LTS_SCHEMA_VERSION: &str = "1.11.0";

fn parse_version(version: &str) -> Option<(u32, u32, u32)> {
  let parts: Vec<&str> = version.split('.').collect();
  if parts.len() != 3 {
    return None;
  }

  let major = parts[0].parse::<u32>().ok()?;
  let minor = parts[1].parse::<u32>().ok()?;
  let patch = parts[2].parse::<u32>().ok()?;

  Some((major, minor, patch))
}

fn is_version_older_than(version: &str, minimum: &str) -> bool {
  let Some(ver) = parse_version(version) else {
    return false;
  };
  let Some(min) = parse_version(minimum) else {
    return false;
  };

  ver < min
}

pub fn schema_version_should_be_current<'a>(
  data: &'a serde_json::Value,
) -> Option<ViolationDetails> {
  let schema_version = data.get("schema_version").and_then(|sv| sv.as_str());

  let Some(schema_version) = schema_version else {
    return Some(ViolationDetails { details: None });
  };

  if is_version_older_than(schema_version, EARLIEST_LTS_SCHEMA_VERSION) {
    return Some(ViolationDetails {
      details: Some(format!(
        "schema_version={}, earliest_supported={}",
        schema_version, EARLIEST_LTS_SCHEMA_VERSION
      )),
    });
  }
  None
}

#[cfg(test)]
mod tests {
  use super::*;
  use serde_json::json;

  #[test]
  fn test_parse_version_valid() {
    assert_eq!(parse_version("1.11.0"), Some((1, 11, 0)));
    assert_eq!(parse_version("2.0.0"), Some((2, 0, 0)));
    assert_eq!(parse_version("1.10.5"), Some((1, 10, 5)));
  }

  #[test]
  fn test_parse_version_invalid() {
    assert_eq!(parse_version("1.11"), None);
    assert_eq!(parse_version("1.11.0.0"), None);
    assert_eq!(parse_version("invalid"), None);
    assert_eq!(parse_version("1.a.0"), None);
  }

  #[test]
  fn test_is_version_older_than() {
    // Older versions
    assert!(is_version_older_than("1.10.0", "1.11.0"));
    assert!(is_version_older_than("1.9.5", "1.11.0"));
    assert!(is_version_older_than("0.9.0", "1.11.0"));

    // Same or newer versions
    assert!(!is_version_older_than("1.11.0", "1.11.0"));
    assert!(!is_version_older_than("1.12.0", "1.11.0"));
    assert!(!is_version_older_than("2.0.0", "1.11.0"));
    assert!(!is_version_older_than("1.11.1", "1.11.0"));
  }

  #[test]
  fn test_schema_version_too_old() {
    let data = json!({
      "schema_version": "1.10.0"
    });
    let result = schema_version_should_be_current(&data);
    assert!(result.is_some());
    let details = result.unwrap().details.unwrap();
    assert!(details.contains("1.10.0"));
    assert!(details.contains("1.11.0"));
  }

  #[test]
  fn test_schema_version_at_earliest_lts() {
    let data = json!({
      "schema_version": "1.11.0"
    });
    let result = schema_version_should_be_current(&data);
    assert!(result.is_none());
  }

  #[test]
  fn test_schema_version_newer_than_lts() {
    let data = json!({
      "schema_version": "2.0.0"
    });
    let result = schema_version_should_be_current(&data);
    assert!(result.is_none());
  }

  #[test]
  fn test_schema_version_missing() {
    let data = json!({});
    let result = schema_version_should_be_current(&data);
    assert!(result.is_some());
    assert!(result.unwrap().details.is_none());
  }
}