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