config_disassembler/
meta.rs1use std::fs;
8use std::path::Path;
9
10use serde::{Deserialize, Serialize};
11
12use crate::error::Result;
13use crate::format::Format;
14
15pub const META_FILENAME: &str = ".config-disassembler.json";
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Meta {
21 pub source_format: SerdeFormat,
23 pub file_format: SerdeFormat,
25 pub source_filename: Option<String>,
28 pub root: Root,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(tag = "kind", rename_all = "snake_case")]
35pub enum Root {
36 Object {
38 key_order: Vec<String>,
40 key_files: std::collections::BTreeMap<String, String>,
46 main_file: Option<String>,
49 },
50 Array {
52 files: Vec<String>,
55 },
56}
57
58pub type SerdeFormat = Format;
63
64impl Meta {
65 pub fn write(&self, dir: &Path) -> Result<()> {
67 let path = dir.join(META_FILENAME);
68 let text = serde_json::to_string_pretty(self)?;
69 fs::write(path, text)?;
70 Ok(())
71 }
72
73 pub fn read(dir: &Path) -> Result<Self> {
75 let path = dir.join(META_FILENAME);
76 let text = fs::read_to_string(&path).map_err(|e| {
77 crate::error::Error::Invalid(format!(
78 "could not read metadata file {}: {e}",
79 path.display()
80 ))
81 })?;
82 Ok(serde_json::from_str(&text)?)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn serde_format_round_trip() {
92 for &fmt in Format::ALL {
93 let text = serde_json::to_string(&fmt).unwrap();
94 let back: SerdeFormat = serde_json::from_str(&text).unwrap();
95 assert_eq!(fmt, back);
96 }
97 }
98
99 #[test]
100 fn read_returns_error_on_malformed_json() {
101 let tmp = tempfile::tempdir().unwrap();
102 std::fs::write(tmp.path().join(META_FILENAME), "{ not valid json }").unwrap();
103 let err = Meta::read(tmp.path()).unwrap_err();
104 assert!(err.to_string().contains("json"), "got: {err}");
106 }
107
108 #[test]
109 fn read_returns_invalid_when_missing() {
110 let tmp = tempfile::tempdir().unwrap();
111 let err = Meta::read(tmp.path()).unwrap_err();
112 assert!(err.to_string().contains("metadata"));
113 }
114
115 #[test]
116 fn write_and_read_round_trip_object_root() {
117 let tmp = tempfile::tempdir().unwrap();
118 let meta = Meta {
119 source_format: SerdeFormat::Json,
120 file_format: SerdeFormat::Yaml,
121 source_filename: Some("orig.json".into()),
122 root: Root::Object {
123 key_order: vec!["a".into(), "b".into()],
124 key_files: std::collections::BTreeMap::new(),
125 main_file: Some("_main.yaml".into()),
126 },
127 };
128 meta.write(tmp.path()).unwrap();
129 let back = Meta::read(tmp.path()).unwrap();
130 assert!(matches!(back.root, Root::Object { .. }));
131 }
132
133 #[test]
134 fn write_and_read_round_trip_array_root() {
135 let tmp = tempfile::tempdir().unwrap();
136 let meta = Meta {
137 source_format: SerdeFormat::Yaml,
138 file_format: SerdeFormat::Json5,
139 source_filename: None,
140 root: Root::Array {
141 files: vec!["1.json5".into(), "2.json5".into()],
142 },
143 };
144 meta.write(tmp.path()).unwrap();
145 let back = Meta::read(tmp.path()).unwrap();
146 match back.root {
147 Root::Array { files } => assert_eq!(files.len(), 2),
148 _ => panic!("expected array root"),
149 }
150 }
151}