use std::fs;
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::format::Format;
pub const META_FILENAME: &str = ".config-disassembler.json";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Meta {
pub source_format: SerdeFormat,
pub file_format: SerdeFormat,
pub source_filename: Option<String>,
pub root: Root,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Root {
Object {
key_order: Vec<String>,
key_files: std::collections::BTreeMap<String, String>,
main_file: Option<String>,
},
Array {
files: Vec<String>,
},
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SerdeFormat {
Json,
Json5,
Yaml,
Toml,
}
impl From<Format> for SerdeFormat {
fn from(f: Format) -> Self {
match f {
Format::Json => SerdeFormat::Json,
Format::Json5 => SerdeFormat::Json5,
Format::Yaml => SerdeFormat::Yaml,
Format::Toml => SerdeFormat::Toml,
}
}
}
impl From<SerdeFormat> for Format {
fn from(f: SerdeFormat) -> Self {
match f {
SerdeFormat::Json => Format::Json,
SerdeFormat::Json5 => Format::Json5,
SerdeFormat::Yaml => Format::Yaml,
SerdeFormat::Toml => Format::Toml,
}
}
}
impl Meta {
pub fn write(&self, dir: &Path) -> Result<()> {
let path = dir.join(META_FILENAME);
let text = serde_json::to_string_pretty(self)?;
fs::write(path, text)?;
Ok(())
}
pub fn read(dir: &Path) -> Result<Self> {
let path = dir.join(META_FILENAME);
let text = fs::read_to_string(&path).map_err(|e| {
crate::error::Error::Invalid(format!(
"could not read metadata file {}: {e}",
path.display()
))
})?;
Ok(serde_json::from_str(&text)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_format_round_trip() {
for fmt in [Format::Json, Format::Json5, Format::Yaml, Format::Toml] {
let s: SerdeFormat = fmt.into();
let back: Format = s.into();
assert_eq!(fmt, back);
}
}
#[test]
fn read_returns_invalid_when_missing() {
let tmp = tempfile::tempdir().unwrap();
let err = Meta::read(tmp.path()).unwrap_err();
assert!(err.to_string().contains("metadata"));
}
#[test]
fn write_and_read_round_trip_object_root() {
let tmp = tempfile::tempdir().unwrap();
let meta = Meta {
source_format: SerdeFormat::Json,
file_format: SerdeFormat::Yaml,
source_filename: Some("orig.json".into()),
root: Root::Object {
key_order: vec!["a".into(), "b".into()],
key_files: std::collections::BTreeMap::new(),
main_file: Some("_main.yaml".into()),
},
};
meta.write(tmp.path()).unwrap();
let back = Meta::read(tmp.path()).unwrap();
assert!(matches!(back.root, Root::Object { .. }));
}
#[test]
fn write_and_read_round_trip_array_root() {
let tmp = tempfile::tempdir().unwrap();
let meta = Meta {
source_format: SerdeFormat::Yaml,
file_format: SerdeFormat::Json5,
source_filename: None,
root: Root::Array {
files: vec!["1.json5".into(), "2.json5".into()],
},
};
meta.write(tmp.path()).unwrap();
let back = Meta::read(tmp.path()).unwrap();
match back.root {
Root::Array { files } => assert_eq!(files.len(), 2),
_ => panic!("expected array root"),
}
}
}