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 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub indent: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(tag = "kind", rename_all = "snake_case")]
41pub enum Root {
42 Object {
44 key_order: Vec<String>,
46 key_files: std::collections::BTreeMap<String, String>,
52 main_file: Option<String>,
55 },
56 Array {
58 files: Vec<String>,
61 },
62}
63
64pub type SerdeFormat = Format;
69
70impl Meta {
71 pub fn write(&self, dir: &Path) -> Result<()> {
73 let path = dir.join(META_FILENAME);
74 let text = serde_json::to_string_pretty(self)?;
75 fs::write(path, text)?;
76 Ok(())
77 }
78
79 pub fn read(dir: &Path) -> Result<Self> {
81 let path = dir.join(META_FILENAME);
82 let text = fs::read_to_string(&path).map_err(|e| {
83 crate::error::Error::Invalid(format!(
84 "could not read metadata file {}: {e}",
85 path.display()
86 ))
87 })?;
88 Ok(serde_json::from_str(&text)?)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn serde_format_round_trip() {
98 for &fmt in Format::ALL {
99 let text = serde_json::to_string(&fmt).unwrap();
100 let back: SerdeFormat = serde_json::from_str(&text).unwrap();
101 assert_eq!(fmt, back);
102 }
103 }
104
105 #[test]
106 fn read_returns_error_on_malformed_json() {
107 let tmp = tempfile::tempdir().unwrap();
108 std::fs::write(tmp.path().join(META_FILENAME), "{ not valid json }").unwrap();
109 let err = Meta::read(tmp.path()).unwrap_err();
110 assert!(err.to_string().contains("json"), "got: {err}");
112 }
113
114 #[test]
115 fn read_returns_invalid_when_missing() {
116 let tmp = tempfile::tempdir().unwrap();
117 let err = Meta::read(tmp.path()).unwrap_err();
118 assert!(err.to_string().contains("metadata"));
119 }
120
121 #[test]
122 fn write_and_read_round_trip_object_root() {
123 let tmp = tempfile::tempdir().unwrap();
124 let meta = Meta {
125 source_format: SerdeFormat::Json,
126 file_format: SerdeFormat::Yaml,
127 source_filename: Some("orig.json".into()),
128 root: Root::Object {
129 key_order: vec!["a".into(), "b".into()],
130 key_files: std::collections::BTreeMap::new(),
131 main_file: Some("_main.yaml".into()),
132 },
133 indent: None,
134 };
135 meta.write(tmp.path()).unwrap();
136 let back = Meta::read(tmp.path()).unwrap();
137 assert!(matches!(back.root, Root::Object { .. }));
138 }
139
140 #[test]
141 fn write_and_read_round_trip_array_root() {
142 let tmp = tempfile::tempdir().unwrap();
143 let meta = Meta {
144 source_format: SerdeFormat::Yaml,
145 file_format: SerdeFormat::Json5,
146 source_filename: None,
147 root: Root::Array {
148 files: vec!["1.json5".into(), "2.json5".into()],
149 },
150 indent: None,
151 };
152 meta.write(tmp.path()).unwrap();
153 let back = Meta::read(tmp.path()).unwrap();
154 match back.root {
155 Root::Array { files } => assert_eq!(files.len(), 2),
156 _ => panic!("expected array root"),
157 }
158 }
159}