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                locale_catalog: None,
72                execution: &sora_execution::ExecutionContext::default(),
73                options: Default::default(),
74                output: ExportOutput::Directory(out_dir.clone()),
75            })
76            .unwrap();
77
78        let content = fs::read_to_string(out_dir.join("Item.json")).unwrap();
79        assert!(content.contains("\"name\": \"Item\""));
80        assert!(content.contains("\"Iron Sword\""));
81
82        let _ = fs::remove_dir_all(out_dir);
83    }
84
85    fn example_ir() -> ConfigIr {
86        let schema: SchemaFile = toml::from_str(
87            r#"
88package = "game_config"
89
90[[tables]]
91name = "Item"
92mode = "map"
93key = "id"
94
95[[tables.fields]]
96name = "id"
97type = "i32"
98"#,
99        )
100        .unwrap();
101        normalize_schema(schema).unwrap()
102    }
103
104    fn example_data() -> ConfigData {
105        ConfigData {
106            tables: vec![TableData {
107                name: "Item".to_owned(),
108                rows: vec![RowData {
109                    values: BTreeMap::from([
110                        ("id".to_owned(), Value::Integer(1001)),
111                        ("name".to_owned(), Value::String("Iron Sword".to_owned())),
112                    ]),
113                }],
114            }],
115        }
116    }
117
118    fn temp_dir() -> PathBuf {
119        static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
120        let unique = SystemTime::now()
121            .duration_since(UNIX_EPOCH)
122            .unwrap()
123            .as_nanos();
124        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
125        std::env::temp_dir().join(format!("sora-export-debug-json-test-{unique}-{id}"))
126    }
127}