use serde_json::Value;
use super::version::GenomeSchemaVersion;
use crate::types::{EvoError, EvoResult};
pub fn detect_schema_version(genome: &Value) -> EvoResult<GenomeSchemaVersion> {
if let Some(integer) = genome.get("genome_schema_version").and_then(|v| v.as_u64()) {
let narrowed: u32 = integer.try_into().map_err(|_| {
EvoError::InvalidGenome(format!(
"genome_schema_version {} exceeds u32 range",
integer
))
})?;
return Ok(GenomeSchemaVersion(narrowed));
}
let legacy = genome.get("version").and_then(|v| v.as_str());
match legacy {
Some("2.0") | Some("2.1") => Ok(GenomeSchemaVersion(2)),
Some("3.0") => Ok(GenomeSchemaVersion(3)),
Some(other) => Err(EvoError::InvalidGenome(format!(
"Unsupported legacy genome version string '{other}'. Expected '2.0', '2.1', or \
'3.0', or an explicit integer `genome_schema_version` field."
))),
None => Err(EvoError::InvalidGenome(
"Genome is missing both `genome_schema_version` (integer) and `version` (legacy \
string)"
.to_string(),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn integer_field_takes_priority_over_legacy_string() {
let g = json!({ "genome_schema_version": 7, "version": "2.0" });
assert_eq!(detect_schema_version(&g).unwrap(), GenomeSchemaVersion(7));
}
#[test]
fn legacy_2_0_string_maps_to_two() {
let g = json!({ "version": "2.0" });
assert_eq!(detect_schema_version(&g).unwrap(), GenomeSchemaVersion(2));
}
#[test]
fn legacy_3_0_string_maps_to_three() {
let g = json!({ "version": "3.0" });
assert_eq!(detect_schema_version(&g).unwrap(), GenomeSchemaVersion(3));
}
#[test]
fn explicit_integer_alone_is_sufficient() {
let g = json!({ "genome_schema_version": 3 });
assert_eq!(detect_schema_version(&g).unwrap(), GenomeSchemaVersion(3));
}
#[test]
fn unsupported_legacy_string_is_rejected() {
let g = json!({ "version": "1.0" });
assert!(matches!(
detect_schema_version(&g),
Err(EvoError::InvalidGenome(_))
));
}
#[test]
fn legacy_v0_0_1_string_is_rejected() {
let g = json!({ "version": "v0.0.1" });
assert!(matches!(
detect_schema_version(&g),
Err(EvoError::InvalidGenome(_))
));
}
#[test]
fn missing_both_fields_is_rejected() {
let g = json!({});
assert!(matches!(
detect_schema_version(&g),
Err(EvoError::InvalidGenome(_))
));
}
#[test]
fn integer_above_u32_is_rejected() {
let g = json!({ "genome_schema_version": (u32::MAX as u64 + 1) });
assert!(matches!(
detect_schema_version(&g),
Err(EvoError::InvalidGenome(_))
));
}
#[test]
fn shape_sniffing_is_not_attempted() {
let g = json!({ "blueprint": {}, "brain_regions": {} });
assert!(matches!(
detect_schema_version(&g),
Err(EvoError::InvalidGenome(_))
));
}
#[test]
fn legacy_2_1_string_maps_to_two() {
let g = json!({ "version": "2.1" });
assert_eq!(detect_schema_version(&g).unwrap(), GenomeSchemaVersion(2));
}
#[test]
fn legacy_2_5_string_is_rejected() {
let g = json!({ "version": "2.5" });
assert!(matches!(
detect_schema_version(&g),
Err(EvoError::InvalidGenome(_))
));
}
#[test]
fn integer_field_of_wrong_type_falls_through_to_legacy() {
let g = json!({ "genome_schema_version": "three", "version": "3.0" });
assert_eq!(detect_schema_version(&g).unwrap(), GenomeSchemaVersion(3));
}
}