agentic_sdk/
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> {
139 match magic {
140 b"AMEM" => Some(SisterType::Memory),
141 b"AVIS" => Some(SisterType::Vision),
142 b"ACDB" => Some(SisterType::Codebase),
143 b"ATIM" => Some(SisterType::Time),
144 b"ACON" => Some(SisterType::Contract),
145 _ => None,
146 }
147}
148
149pub fn is_json_format(path: &Path) -> SisterResult<bool> {
153 use std::io::Read;
154 let mut file = std::fs::File::open(path)?;
155 let mut buf = [0u8; 64];
156 let n = file.read(&mut buf).map_err(|e| {
157 SisterError::new(
158 ErrorCode::StorageError,
159 format!("Failed to read file: {}", e),
160 )
161 })?;
162 let slice = &buf[..n];
163 Ok(slice.iter().find(|b| !b.is_ascii_whitespace()) == Some(&b'{'))
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_identify_sister_by_magic() {
172 assert_eq!(identify_sister_by_magic(b"AMEM"), Some(SisterType::Memory));
173 assert_eq!(identify_sister_by_magic(b"AVIS"), Some(SisterType::Vision));
174 assert_eq!(
175 identify_sister_by_magic(b"ACDB"),
176 Some(SisterType::Codebase)
177 );
178 assert_eq!(identify_sister_by_magic(b"ATIM"), Some(SisterType::Time));
179 assert_eq!(
180 identify_sister_by_magic(b"ACON"),
181 Some(SisterType::Contract)
182 );
183 assert_eq!(identify_sister_by_magic(b"XXXX"), None);
184 assert_eq!(identify_sister_by_magic(b"AGNT"), None); }
186
187 #[test]
188 fn test_version_compatibility() {
189 let v1 = Version::new(1, 0, 0);
190 let v2 = Version::new(2, 0, 0);
191 let v1_1 = Version::new(1, 1, 0);
192
193 assert!(VersionCompatibility::can_read(&v2, &v1));
194 assert!(!VersionCompatibility::can_read(&v1, &v2));
195 assert!(VersionCompatibility::is_compatible(&v1, &v1_1));
196 assert!(VersionCompatibility::needs_migration(&v2, &v1));
197 }
198}