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