use crate::atlas::AtlasMetadata;
use crate::export::{ExportError, ExportOptions, Exporter};
use std::fs::File;
use std::io::Write;
use std::path::Path;
#[derive(Debug, Default)]
pub struct JsonExporter;
impl JsonExporter {
pub fn new() -> Self {
Self
}
pub fn export_to_string(
&self,
metadata: &AtlasMetadata,
options: &ExportOptions,
) -> Result<String, ExportError> {
let json = if options.pretty {
serde_json::to_string_pretty(metadata)?
} else {
serde_json::to_string(metadata)?
};
Ok(json)
}
}
impl Exporter for JsonExporter {
fn export(
&self,
metadata: &AtlasMetadata,
output_path: &Path,
options: &ExportOptions,
) -> Result<(), ExportError> {
let json = self.export_to_string(metadata, options)?;
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut file = File::create(output_path)?;
file.write_all(json.as_bytes())?;
Ok(())
}
fn format_name(&self) -> &'static str {
"json"
}
fn extension(&self) -> &'static str {
"json"
}
}
pub fn export_json(
metadata: &AtlasMetadata,
output_path: &Path,
pretty: bool,
) -> Result<(), ExportError> {
let exporter = JsonExporter::new();
let options = ExportOptions { pretty, ..Default::default() };
exporter.export(metadata, output_path, &options)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::atlas::{AtlasAnimation, AtlasBox, AtlasFrame};
use std::collections::HashMap;
use tempfile::TempDir;
fn create_test_metadata() -> AtlasMetadata {
AtlasMetadata {
image: "test.png".to_string(),
size: [64, 64],
frames: HashMap::from([
(
"sprite1".to_string(),
AtlasFrame { x: 0, y: 0, w: 16, h: 16, origin: None, boxes: None },
),
(
"sprite2".to_string(),
AtlasFrame {
x: 16,
y: 0,
w: 16,
h: 16,
origin: Some([8, 16]),
boxes: Some(HashMap::from([(
"hit".to_string(),
AtlasBox { x: 2, y: 2, w: 12, h: 12 },
)])),
},
),
]),
animations: HashMap::from([(
"idle".to_string(),
AtlasAnimation {
frames: vec!["sprite1".to_string(), "sprite2".to_string()],
fps: 10,
tags: None,
},
)]),
}
}
#[test]
fn test_json_exporter_new() {
let exporter = JsonExporter::new();
assert_eq!(exporter.format_name(), "json");
assert_eq!(exporter.extension(), "json");
}
#[test]
fn test_export_to_string_pretty() {
let exporter = JsonExporter::new();
let metadata = create_test_metadata();
let options = ExportOptions { pretty: true, ..Default::default() };
let json = exporter.export_to_string(&metadata, &options).unwrap();
assert!(json.contains('\n'));
assert!(json.contains(" ")); assert!(json.contains("\"image\""));
assert!(json.contains("\"test.png\""));
assert!(json.contains("\"frames\""));
assert!(json.contains("\"sprite1\""));
assert!(json.contains("\"sprite2\""));
assert!(json.contains("\"animations\""));
assert!(json.contains("\"idle\""));
}
#[test]
fn test_export_to_string_compact() {
let exporter = JsonExporter::new();
let metadata = create_test_metadata();
let options = ExportOptions { pretty: false, ..Default::default() };
let json = exporter.export_to_string(&metadata, &options).unwrap();
assert!(!json.contains(" ")); assert!(json.contains("\"image\":\"test.png\""));
}
#[test]
fn test_export_to_file() {
let temp = TempDir::new().unwrap();
let output_path = temp.path().join("atlas.json");
let exporter = JsonExporter::new();
let metadata = create_test_metadata();
let options = ExportOptions::default();
exporter.export(&metadata, &output_path, &options).unwrap();
assert!(output_path.exists());
let content = std::fs::read_to_string(&output_path).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(parsed["image"], "test.png");
assert_eq!(parsed["size"][0], 64);
assert_eq!(parsed["size"][1], 64);
}
#[test]
fn test_export_creates_directories() {
let temp = TempDir::new().unwrap();
let output_path = temp.path().join("nested/dir/atlas.json");
let exporter = JsonExporter::new();
let metadata = create_test_metadata();
let options = ExportOptions::default();
exporter.export(&metadata, &output_path, &options).unwrap();
assert!(output_path.exists());
}
#[test]
fn test_export_json_convenience_function() {
let temp = TempDir::new().unwrap();
let output_path = temp.path().join("atlas.json");
let metadata = create_test_metadata();
export_json(&metadata, &output_path, true).unwrap();
assert!(output_path.exists());
let content = std::fs::read_to_string(&output_path).unwrap();
assert!(content.contains('\n')); }
#[test]
fn test_export_preserves_optional_fields() {
let exporter = JsonExporter::new();
let metadata = create_test_metadata();
let options = ExportOptions::default();
let json = exporter.export_to_string(&metadata, &options).unwrap();
assert!(json.contains("\"origin\""));
assert!(json.contains("\"boxes\""));
assert!(json.contains("\"hit\""));
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
let sprite2 = &parsed["frames"]["sprite2"];
assert_eq!(sprite2["origin"][0], 8);
assert_eq!(sprite2["origin"][1], 16);
assert!(sprite2["boxes"]["hit"].is_object());
}
#[test]
fn test_export_skips_none_fields() {
let exporter = JsonExporter::new();
let metadata = AtlasMetadata {
image: "simple.png".to_string(),
size: [32, 32],
frames: HashMap::from([(
"sprite".to_string(),
AtlasFrame { x: 0, y: 0, w: 32, h: 32, origin: None, boxes: None },
)]),
animations: HashMap::new(),
};
let options = ExportOptions::default();
let json = exporter.export_to_string(&metadata, &options).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
let sprite = &parsed["frames"]["sprite"];
assert!(sprite.get("origin").is_none());
assert!(sprite.get("boxes").is_none());
assert!(parsed.get("animations").is_none());
}
#[test]
fn test_export_empty_metadata() {
let exporter = JsonExporter::new();
let metadata = AtlasMetadata {
image: "empty.png".to_string(),
size: [0, 0],
frames: HashMap::new(),
animations: HashMap::new(),
};
let options = ExportOptions::default();
let result = exporter.export_to_string(&metadata, &options);
assert!(result.is_ok());
let json = result.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["image"], "empty.png");
assert!(parsed["frames"].as_object().unwrap().is_empty());
}
#[test]
fn test_roundtrip_json() {
let exporter = JsonExporter::new();
let original = create_test_metadata();
let options = ExportOptions::default();
let json = exporter.export_to_string(&original, &options).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["image"], original.image);
assert_eq!(parsed["size"][0], original.size[0]);
assert_eq!(parsed["size"][1], original.size[1]);
assert_eq!(parsed["frames"].as_object().unwrap().len(), original.frames.len());
assert_eq!(parsed["animations"].as_object().unwrap().len(), original.animations.len());
}
}