sora_export/
debug_json.rs1use 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}