1use std::fmt;
12use std::path::Path;
13
14use prost::Message;
15
16use crate::schema::ModDefDocument;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum DocumentFormat {
20 Yaml,
21 Json,
22 Binary,
23}
24
25#[derive(Debug)]
28pub struct ParseError(String);
29
30impl ParseError {
31 pub fn new(msg: impl Into<String>) -> Self {
32 ParseError(msg.into())
33 }
34}
35
36impl fmt::Display for ParseError {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 f.write_str(&self.0)
39 }
40}
41
42impl std::error::Error for ParseError {}
43
44pub fn detect_format(path: &str) -> Result<DocumentFormat, ParseError> {
46 if path.ends_with(".moddef.yaml") || path.ends_with(".moddef.yml") {
47 Ok(DocumentFormat::Yaml)
48 } else if path.ends_with(".moddef.json") {
49 Ok(DocumentFormat::Json)
50 } else if path.ends_with(".moddef") {
51 Ok(DocumentFormat::Binary)
52 } else {
53 Err(ParseError::new(format!(
54 "cannot detect ModDef format from path: {path}"
55 )))
56 }
57}
58
59pub fn parse_document(data: &[u8], format: DocumentFormat) -> Result<ModDefDocument, ParseError> {
61 match format {
62 DocumentFormat::Binary => ModDefDocument::decode(data)
63 .map_err(|e| ParseError::new(format!("failed to parse ModDef binary document: {e}"))),
64 DocumentFormat::Json => serde_json::from_slice(data)
65 .map_err(|e| ParseError::new(format!("failed to parse ModDef json document: {e}"))),
66 DocumentFormat::Yaml => {
67 let y: serde_yaml::Value = serde_yaml::from_slice(data).map_err(|e| {
68 ParseError::new(format!("failed to parse ModDef yaml document: {e}"))
69 })?;
70 let j = yaml_to_json(y)?;
71 serde_json::from_value(j)
72 .map_err(|e| ParseError::new(format!("failed to parse ModDef yaml document: {e}")))
73 }
74 }
75}
76
77pub fn serialize_document(
80 doc: &ModDefDocument,
81 format: DocumentFormat,
82) -> Result<Vec<u8>, ParseError> {
83 match format {
84 DocumentFormat::Binary => Ok(doc.encode_to_vec()),
85 DocumentFormat::Json => {
86 let mut out = serde_json::to_vec_pretty(doc)
87 .map_err(|e| ParseError::new(format!("failed to serialize json: {e}")))?;
88 out.push(b'\n');
89 Ok(out)
90 }
91 DocumentFormat::Yaml => {
92 let j = serde_json::to_value(doc)
93 .map_err(|e| ParseError::new(format!("failed to serialize yaml: {e}")))?;
94 serde_yaml::to_string(&j)
95 .map(String::into_bytes)
96 .map_err(|e| ParseError::new(format!("failed to serialize yaml: {e}")))
97 }
98 }
99}
100
101pub fn load(path: impl AsRef<Path>) -> Result<ModDefDocument, ParseError> {
103 let path = path.as_ref();
104 let format = detect_format(&path.to_string_lossy())?;
105 let data = std::fs::read(path)
106 .map_err(|e| ParseError::new(format!("failed to read {}: {e}", path.display())))?;
107 parse_document(&data, format)
108}
109
110pub fn save(doc: &ModDefDocument, path: impl AsRef<Path>) -> Result<(), ParseError> {
112 let path = path.as_ref();
113 let format = detect_format(&path.to_string_lossy())?;
114 let data = serialize_document(doc, format)?;
115 std::fs::write(path, data)
116 .map_err(|e| ParseError::new(format!("failed to write {}: {e}", path.display())))
117}
118
119fn yaml_to_json(v: serde_yaml::Value) -> Result<serde_json::Value, ParseError> {
123 use serde_json::Value as J;
124 use serde_yaml::Value as Y;
125 Ok(match v {
126 Y::Null => J::Null,
127 Y::Bool(b) => J::Bool(b),
128 Y::Number(n) => {
129 if let Some(i) = n.as_i64() {
130 J::from(i)
131 } else if let Some(u) = n.as_u64() {
132 J::from(u)
133 } else if let Some(f) = n.as_f64() {
134 serde_json::Number::from_f64(f)
135 .map(J::Number)
136 .ok_or_else(|| ParseError::new("non-finite number in yaml document"))?
137 } else {
138 return Err(ParseError::new("unrepresentable number in yaml document"));
139 }
140 }
141 Y::String(s) => J::String(s),
142 Y::Sequence(seq) => J::Array(
143 seq.into_iter()
144 .map(yaml_to_json)
145 .collect::<Result<_, _>>()?,
146 ),
147 Y::Mapping(m) => {
148 let mut obj = serde_json::Map::with_capacity(m.len());
149 for (k, v) in m {
150 let key = match k {
151 Y::String(s) => s,
152 Y::Number(n) => n.to_string(),
153 Y::Bool(b) => b.to_string(),
154 other => {
155 return Err(ParseError::new(format!(
156 "unsupported yaml mapping key: {other:?}"
157 )))
158 }
159 };
160 obj.insert(key, yaml_to_json(v)?);
161 }
162 J::Object(obj)
163 }
164 Y::Tagged(t) => yaml_to_json(t.value)?,
165 })
166}