feagi_evolutionary/genome/
artifact.rs1use crate::{types::EvoError, EvoResult};
11use serde_json::Value;
12
13pub const GENOME_ARTIFACT_EXTENSION: &str = "genome";
15
16pub const GENOME_ARTIFACT_MEDIA_TYPE: &str = "application/vnd.feagi.genome+json";
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum GenomeArtifactEncoding {
25 Json,
27}
28
29pub trait GenomeArtifactCodec: Send + Sync {
31 fn encoding(&self) -> GenomeArtifactEncoding;
33
34 fn media_type(&self) -> &'static str;
36
37 fn decode(&self, artifact: &[u8]) -> EvoResult<Value>;
39
40 fn encode(&self, genome: &Value) -> EvoResult<Vec<u8>>;
42}
43
44#[derive(Clone, Copy, Debug, Default)]
46pub struct JsonGenomeArtifactCodec;
47
48impl GenomeArtifactCodec for JsonGenomeArtifactCodec {
49 fn encoding(&self) -> GenomeArtifactEncoding {
50 GenomeArtifactEncoding::Json
51 }
52
53 fn media_type(&self) -> &'static str {
54 GENOME_ARTIFACT_MEDIA_TYPE
55 }
56
57 fn decode(&self, artifact: &[u8]) -> EvoResult<Value> {
58 serde_json::from_slice(artifact)
59 .map_err(|error| EvoError::InvalidGenome(format!("Failed to parse JSON: {error}")))
60 }
61
62 fn encode(&self, genome: &Value) -> EvoResult<Vec<u8>> {
63 serde_json::to_vec(genome).map_err(EvoError::from)
64 }
65}
66
67pub fn decode_genome_artifact(artifact: &[u8]) -> EvoResult<Value> {
69 JsonGenomeArtifactCodec.decode(artifact)
70}
71
72pub fn encode_genome_artifact(genome: &Value) -> EvoResult<Vec<u8>> {
74 JsonGenomeArtifactCodec.encode(genome)
75}
76
77pub fn validate_genome_artifact_file_name(file_name: &str) -> EvoResult<()> {
79 let leaf_name = file_name.rsplit(['/', '\\']).next().unwrap_or(file_name);
80 let valid_extension = leaf_name.rsplit_once('.').is_some_and(|(stem, extension)| {
81 !stem.is_empty() && extension.eq_ignore_ascii_case(GENOME_ARTIFACT_EXTENSION)
82 });
83
84 if valid_extension {
85 Ok(())
86 } else {
87 Err(EvoError::InvalidGenome(
88 "Genome files must use the .genome extension".to_string(),
89 ))
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use serde_json::json;
97
98 #[test]
99 fn json_codec_round_trips_without_changing_schema_version() {
100 let genome = json!({
101 "genome_schema_version": 3,
102 "version": "3.0",
103 "blueprint": {}
104 });
105
106 let encoded = encode_genome_artifact(&genome).expect("JSON encoding should succeed");
107 let decoded = decode_genome_artifact(&encoded).expect("JSON decoding should succeed");
108
109 assert_eq!(decoded, genome);
110 assert_eq!(decoded["genome_schema_version"], 3);
111 }
112
113 #[test]
114 fn json_codec_rejects_malformed_artifact_bytes() {
115 let result = decode_genome_artifact(b"not-json");
116
117 assert!(result.is_err());
118 }
119
120 #[test]
121 fn artifact_filename_contract_is_platform_independent() {
122 assert!(validate_genome_artifact_file_name("brain.genome").is_ok());
123 assert!(validate_genome_artifact_file_name("brain.GENOME").is_ok());
124 assert!(validate_genome_artifact_file_name(r"C:\brains\brain.genome").is_ok());
125 assert!(validate_genome_artifact_file_name("brain.json").is_err());
126 assert!(validate_genome_artifact_file_name(".genome").is_err());
127 }
128
129 #[test]
130 fn artifact_decode_precedes_existing_schema_migration_chain() {
131 let artifact = br#"{
132 "genome_id": "artifact-test",
133 "genome_title": "Artifact test",
134 "genome_description": "Codec and schema integration",
135 "version": "2.0",
136 "blueprint": {},
137 "brain_regions": {},
138 "neuron_morphologies": {},
139 "physiology": {"simulation_timestep": 0.025, "max_age": 1},
140 "stats": {
141 "innate_cortical_area_count": 0,
142 "innate_neuron_count": 0,
143 "innate_synapse_count": 0
144 },
145 "signatures": {"genome": "0", "blueprint": "0", "physiology": "0"},
146 "timestamp": 0.0
147 }"#;
148
149 let decoded = decode_genome_artifact(artifact).expect("artifact should decode");
150 let (migrated, report) = crate::genome::migrate_genome_value_to_current(decoded)
151 .expect("existing schema chain should migrate decoded genome");
152
153 assert_eq!(report.from_version.as_u32(), 2);
154 assert_eq!(report.to_version.as_u32(), 3);
155 assert_eq!(migrated["genome_schema_version"], 3);
156 }
157}