agentic_contracts/
file_format.rs1use crate::errors::{ErrorCode, SisterError, SisterResult};
25use crate::types::{SisterType, Version};
26use chrono::{DateTime, Utc};
27use serde::{Deserialize, Serialize};
28use std::path::Path;
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct FileInfo {
36 pub sister_type: SisterType,
38
39 pub version: Version,
41
42 pub created_at: DateTime<Utc>,
44
45 pub updated_at: DateTime<Utc>,
47
48 pub content_length: u64,
50
51 pub needs_migration: bool,
53
54 pub format_id: String,
56}
57
58pub trait FileFormatReader: Sized {
63 fn read_file(path: &Path) -> SisterResult<Self>;
65
66 fn can_read(path: &Path) -> SisterResult<FileInfo>;
69
70 fn file_version(path: &Path) -> SisterResult<Version>;
72
73 fn migrate(data: &[u8], from_version: Version) -> SisterResult<Vec<u8>>;
76}
77
78pub trait FileFormatWriter {
80 fn write_file(&self, path: &Path) -> SisterResult<()>;
82
83 fn to_bytes(&self) -> SisterResult<Vec<u8>>;
85}
86
87#[derive(Debug, Clone)]
91pub struct VersionCompatibility;
92
93impl VersionCompatibility {
94 pub fn can_read(reader_version: &Version, file_version: &Version) -> bool {
98 reader_version.major >= file_version.major
99 }
100
101 pub fn needs_migration(current_version: &Version, file_version: &Version) -> bool {
103 file_version.major < current_version.major
104 }
105
106 pub fn is_compatible(v1: &Version, v2: &Version) -> bool {
108 v1.major == v2.major
109 }
110}
111
112pub fn read_magic_bytes(path: &Path) -> SisterResult<[u8; 4]> {
116 use std::io::Read;
117 let mut file = std::fs::File::open(path)?;
118 let mut magic = [0u8; 4];
119 file.read_exact(&mut magic).map_err(|e| {
120 SisterError::new(
121 ErrorCode::StorageError,
122 format!("Failed to read magic bytes: {}", e),
123 )
124 })?;
125 Ok(magic)
126}
127
128pub fn identify_sister_by_magic(magic: &[u8; 4]) -> Option<SisterType> {
138 match magic {
139 b"AMEM" => Some(SisterType::Memory),
140 b"AVIS" => Some(SisterType::Vision),
141 b"ACDB" => Some(SisterType::Codebase),
142 b"ATIM" => Some(SisterType::Time),
143 _ => None,
144 }
145}
146
147pub fn is_json_format(path: &Path) -> SisterResult<bool> {
151 use std::io::Read;
152 let mut file = std::fs::File::open(path)?;
153 let mut buf = [0u8; 64];
154 let n = file.read(&mut buf).map_err(|e| {
155 SisterError::new(
156 ErrorCode::StorageError,
157 format!("Failed to read file: {}", e),
158 )
159 })?;
160 let slice = &buf[..n];
161 Ok(slice.iter().find(|b| !b.is_ascii_whitespace()) == Some(&b'{'))
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_identify_sister_by_magic() {
170 assert_eq!(identify_sister_by_magic(b"AMEM"), Some(SisterType::Memory));
171 assert_eq!(identify_sister_by_magic(b"AVIS"), Some(SisterType::Vision));
172 assert_eq!(
173 identify_sister_by_magic(b"ACDB"),
174 Some(SisterType::Codebase)
175 );
176 assert_eq!(identify_sister_by_magic(b"ATIM"), Some(SisterType::Time));
177 assert_eq!(identify_sister_by_magic(b"XXXX"), None);
178 assert_eq!(identify_sister_by_magic(b"AGNT"), None); }
180
181 #[test]
182 fn test_version_compatibility() {
183 let v1 = Version::new(1, 0, 0);
184 let v2 = Version::new(2, 0, 0);
185 let v1_1 = Version::new(1, 1, 0);
186
187 assert!(VersionCompatibility::can_read(&v2, &v1));
188 assert!(!VersionCompatibility::can_read(&v1, &v2));
189 assert!(VersionCompatibility::is_compatible(&v1, &v1_1));
190 assert!(VersionCompatibility::needs_migration(&v2, &v1));
191 }
192}