Skip to main content

sora_export/
debug_json.rs

1use serde::Serialize;
2use sora_data::model::TableData;
3use sora_diagnostics::{Result, SoraError};
4
5use crate::{
6    exporter::{DataExporter, ExportOutput, ExportRequest, OutputKind},
7    fs_util::{create_dir_all, write_file},
8};
9
10pub struct DebugJsonExporter;
11
12impl DataExporter for DebugJsonExporter {
13    fn format_name(&self) -> &'static str {
14        "json-debug"
15    }
16
17    fn output_kind(&self) -> OutputKind {
18        OutputKind::Directory
19    }
20
21    fn export(&self, request: ExportRequest<'_>) -> Result<()> {
22        let ExportOutput::Directory(path) = request.output else {
23            return Err(SoraError::InvalidExportOutput {
24                format: self.format_name().to_owned(),
25                expected: "directory",
26            });
27        };
28
29        create_dir_all(&path)?;
30        for table in &request.data.tables {
31            let file_path = path.join(format!("{}.json", table.name));
32            let content = serde_json::to_string_pretty(&DebugTableView { table })
33                .map_err(SoraError::SerializeData)?;
34            write_file(file_path, content)?;
35        }
36
37        Ok(())
38    }
39}
40
41#[derive(Serialize)]
42struct DebugTableView<'a> {
43    table: &'a TableData,
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::exporter::ExportOutput;
50    use sora_data::model::{ConfigData, RowData, TableData, Value};
51    use sora_ir::{model::ConfigIr, normalize::normalize_schema};
52    use sora_schema::model::SchemaFile;
53    use std::{
54        collections::BTreeMap,
55        fs,
56        path::PathBuf,
57        sync::atomic::{AtomicUsize, Ordering},
58        time::{SystemTime, UNIX_EPOCH},
59    };
60
61    #[test]
62    fn debug_json_exporter_writes_table_file() {
63        let ir = example_ir();
64        let data = example_data();
65        let out_dir = temp_dir();
66
67        DebugJsonExporter
68            .export(ExportRequest {
69                ir: &ir,
70                data: &data,
71                execution: &sora_execution::ExecutionContext::default(),
72                options: Default::default(),
73                output: ExportOutput::Directory(out_dir.clone()),
74            })
75            .unwrap();
76
77        let content = fs::read_to_string(out_dir.join("Item.json")).unwrap();
78        assert!(content.contains("\"name\": \"Item\""));
79        assert!(content.contains("\"Iron Sword\""));
80
81        let _ = fs::remove_dir_all(out_dir);
82    }
83
84    fn example_ir() -> ConfigIr {
85        let schema: SchemaFile = toml::from_str(
86            r#"
87package = "game_config"
88
89[[tables]]
90name = "Item"
91mode = "map"
92key = "id"
93
94[[tables.fields]]
95name = "id"
96type = "i32"
97"#,
98        )
99        .unwrap();
100        normalize_schema(schema).unwrap()
101    }
102
103    fn example_data() -> ConfigData {
104        ConfigData {
105            tables: vec![TableData {
106                name: "Item".to_owned(),
107                rows: vec![RowData {
108                    values: BTreeMap::from([
109                        ("id".to_owned(), Value::Integer(1001)),
110                        ("name".to_owned(), Value::String("Iron Sword".to_owned())),
111                    ]),
112                }],
113            }],
114        }
115    }
116
117    fn temp_dir() -> PathBuf {
118        static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
119        let unique = SystemTime::now()
120            .duration_since(UNIX_EPOCH)
121            .unwrap()
122            .as_nanos();
123        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
124        std::env::temp_dir().join(format!("sora-export-debug-json-test-{unique}-{id}"))
125    }
126}