use crate::txlog::TxLog;
use std::fs::File;
use std::io::BufReader;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FormatError {
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Unknown format: {0}")]
UnknownFormat(String),
#[error("Format mismatch: expected {expected}, got {actual}")]
FormatMismatch {
expected: String,
actual: String,
},
}
pub type FormatResult<T> = Result<T, FormatError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Format {
#[default]
Json,
JsonCompact,
}
impl Format {
pub fn extension(&self) -> &'static str {
match self {
Format::Json => "json",
Format::JsonCompact => "json",
}
}
pub fn name(&self) -> &'static str {
match self {
Format::Json => "JSON (pretty)",
Format::JsonCompact => "JSON (compact)",
}
}
pub fn is_binary(&self) -> bool {
match self {
Format::Json | Format::JsonCompact => false,
}
}
pub fn from_extension(ext: &str) -> Option<Self> {
match ext.to_lowercase().as_str() {
"json" => Some(Format::Json),
_ => None,
}
}
}
pub trait SessionFormat: Send + Sync {
fn format(&self) -> Format;
fn serialize(&self, log: &TxLog) -> FormatResult<Vec<u8>>;
fn deserialize(&self, data: &[u8]) -> FormatResult<TxLog>;
fn serialize_to_file(&self, log: &TxLog, file: File) -> FormatResult<()>;
fn deserialize_from_reader(&self, reader: BufReader<File>) -> FormatResult<TxLog>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JsonFormat {
compact: bool,
}
impl JsonFormat {
pub fn new() -> Self {
Self { compact: false }
}
pub fn compact() -> Self {
Self { compact: true }
}
}
impl SessionFormat for JsonFormat {
fn format(&self) -> Format {
if self.compact {
Format::JsonCompact
} else {
Format::Json
}
}
fn serialize(&self, log: &TxLog) -> FormatResult<Vec<u8>> {
let json = if self.compact {
serde_json::to_vec(log)?
} else {
serde_json::to_vec_pretty(log)?
};
Ok(json)
}
fn deserialize(&self, data: &[u8]) -> FormatResult<TxLog> {
let log = serde_json::from_slice(data)?;
Ok(log)
}
fn serialize_to_file(&self, log: &TxLog, file: File) -> FormatResult<()> {
if self.compact {
serde_json::to_writer(file, log)?;
} else {
serde_json::to_writer_pretty(file, log)?;
}
Ok(())
}
fn deserialize_from_reader(&self, reader: BufReader<File>) -> FormatResult<TxLog> {
let log = serde_json::from_reader(reader)?;
Ok(log)
}
}
pub fn get_serializer(format: Format) -> Box<dyn SessionFormat> {
match format {
Format::Json => Box::new(JsonFormat::new()),
Format::JsonCompact => Box::new(JsonFormat::compact()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::txlog::{TxAction, TxLog};
fn create_test_log() -> TxLog {
let mut log = TxLog::with_project("/test/project");
log.log(TxAction::GoalSet {
query: "test query".to_string(),
intent_type: "test".to_string(),
confidence: 0.9,
});
log.log(TxAction::MutationApplied {
mutation_type: "Rename".to_string(),
target: "foo -> bar".to_string(),
changes: 3,
mutation_data: None,
file_path: None,
pre_state: None,
post_state: None,
affected_symbols: vec![],
});
log
}
#[test]
fn test_json_roundtrip() {
let log = create_test_log();
let format = JsonFormat::new();
let bytes = format.serialize(&log).unwrap();
let restored = format.deserialize(&bytes).unwrap();
assert_eq!(log.entries().len(), restored.entries().len());
}
#[test]
fn test_json_compact_roundtrip() {
let log = create_test_log();
let format = JsonFormat::compact();
let bytes = format.serialize(&log).unwrap();
let restored = format.deserialize(&bytes).unwrap();
assert_eq!(log.entries().len(), restored.entries().len());
let pretty_bytes = JsonFormat::new().serialize(&log).unwrap();
assert!(bytes.len() < pretty_bytes.len());
}
#[test]
fn test_format_extension() {
assert_eq!(Format::Json.extension(), "json");
assert_eq!(Format::JsonCompact.extension(), "json");
}
#[test]
fn test_format_from_extension() {
assert_eq!(Format::from_extension("json"), Some(Format::Json));
assert_eq!(Format::from_extension("JSON"), Some(Format::Json));
assert_eq!(Format::from_extension("unknown"), None);
}
#[test]
fn test_format_is_binary() {
assert!(!Format::Json.is_binary());
assert!(!Format::JsonCompact.is_binary());
}
}