1use crate::prelude::*;
4
5use core::fmt;
6use core::marker::PhantomData;
7
8use serde::de::{DeserializeOwned, DeserializeSeed};
9
10use crate::bundle::Bundle;
11use crate::error::{ContentError, codes};
12use crate::format::{AssetKind, AssetPath, DecodeCx, Format, TEXT_MAX_BYTES, text};
13
14pub struct Ron<T> {
18 id: &'static str,
19 extensions: &'static [&'static str],
20 kind: PhantomData<fn() -> T>,
21}
22
23impl<T> Ron<T> {
24 pub const fn new() -> Self {
26 Self::named("ron", &["ron"])
27 }
28
29 pub const fn named(id: &'static str, extensions: &'static [&'static str]) -> Self {
31 Self {
32 id,
33 extensions,
34 kind: PhantomData,
35 }
36 }
37}
38
39impl<T> Default for Ron<T> {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl<T> fmt::Debug for Ron<T> {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.debug_struct("Ron")
48 .field("id", &self.id)
49 .field("extensions", &self.extensions)
50 .finish()
51 }
52}
53
54impl<T: AssetKind + DeserializeOwned> Format for Ron<T> {
55 type Asset = T;
56
57 fn id(&self) -> &'static str {
58 self.id
59 }
60
61 fn extensions(&self) -> &'static [&'static str] {
62 self.extensions
63 }
64
65 fn max_bytes(&self) -> usize {
66 TEXT_MAX_BYTES
67 }
68
69 fn decode(
70 &self,
71 file: &AssetPath,
72 bytes: &[u8],
73 _cx: &DecodeCx<'_>,
74 ) -> Result<T, Vec<ContentError>> {
75 parse_ron(file.shown(), text(file, bytes)?).map_err(|e| vec![e])
76 }
77}
78
79pub fn load_ron<T: DeserializeOwned>(bundle: &Bundle, path: &str) -> Result<T, ContentError> {
81 let display = bundle.display_path(path);
82 let text = bundle
83 .file(path)
84 .ok_or_else(|| ContentError::not_found(&display))?;
85 parse_ron(&display, text)
86}
87
88pub fn parse_ron<T: DeserializeOwned>(file: &str, source: &str) -> Result<T, ContentError> {
91 ::ron::from_str(source).map_err(|e| error(file, &e))
92}
93
94pub fn parse_ron_implicit_some<T: DeserializeOwned>(
97 file: &str,
98 source: &str,
99) -> Result<T, ContentError> {
100 ::ron::Options::default()
101 .with_default_extension(::ron::extensions::Extensions::IMPLICIT_SOME)
102 .from_str(source)
103 .map_err(|e| error(file, &e))
104}
105
106pub fn parse_ron_seed<'de, S: DeserializeSeed<'de>>(
109 file: &str,
110 source: &'de str,
111 seed: S,
112) -> Result<S::Value, ContentError> {
113 ::ron::Options::default()
114 .from_str_seed(source, seed)
115 .map_err(|e| error(file, &e))
116}
117
118pub(crate) fn error(file: &str, e: &::ron::error::SpannedError) -> ContentError {
120 let pos = e.span.end;
123 ContentError::new(file, e.code.to_string())
124 .at(to_u32(pos.line), Some(to_u32(pos.col)))
125 .with_code(codes::PARSE)
126}
127
128fn to_u32(n: usize) -> u32 {
129 u32::try_from(n).unwrap_or(u32::MAX)
130}
131
132#[cfg(test)]
133mod tests {
134 use std::collections::BTreeMap;
135
136 use super::*;
137 use crate::bundle::Layer;
138
139 #[test]
140 fn parses_valid_ron() {
141 let m: BTreeMap<String, u8> =
142 parse_ron("x.ron", "{\"a\": 1, \"b\": 2}").unwrap_or_default();
143 assert_eq!(m.get("b"), Some(&2));
144 }
145
146 #[test]
147 fn syntax_error_reports_file_line_and_column() {
148 let src = "{\n \"a\": 1,\n \"b\": ?,\n}";
150 let err = parse_ron::<BTreeMap<String, u8>>("base/data/t.ron", src)
151 .err()
152 .unwrap_or_else(|| ContentError::new("", "expected an error"));
153 assert_eq!(err.file, "base/data/t.ron");
154 assert_eq!(err.line, Some(3));
155 assert_eq!(err.column, Some(10));
156 assert_eq!(err.code, Some(codes::PARSE));
157 assert!(err.to_string().starts_with("base/data/t.ron:3:10: "));
158 }
159
160 #[test]
161 fn type_error_reports_position() {
162 let src = "{\n\"a\": \"not a number\"}";
163 let err = parse_ron::<BTreeMap<String, u8>>("t.ron", src)
164 .err()
165 .unwrap_or_else(|| ContentError::new("", "expected an error"));
166 assert_eq!(err.line, Some(2));
167 }
168
169 #[test]
170 fn huge_positions_saturate() {
171 assert_eq!(to_u32(usize::MAX), u32::MAX);
172 assert_eq!(to_u32(7), 7);
173 }
174
175 static FILES: &[(&str, &[u8])] = &[
176 ("data/bad.ron", b"{\n\"a\": ?}"),
177 ("data/good.ron", b"{\"a\": 1}"),
178 ];
179
180 fn bundle() -> Bundle {
181 Bundle::new(vec![Layer::table("base", FILES)])
182 }
183
184 #[test]
185 fn loads_a_file_of_the_bundle() {
186 let m: Result<BTreeMap<String, u8>, _> = load_ron(&bundle(), "data/good.ron");
187 assert_eq!(m.ok().and_then(|m| m.get("a").copied()), Some(1));
188 }
189
190 #[test]
191 fn an_error_names_the_file_as_the_bundle_shows_it() {
192 let err = load_ron::<BTreeMap<String, u8>>(&bundle(), "data/bad.ron")
193 .err()
194 .unwrap_or_else(|| ContentError::new("", "expected an error"));
195 assert_eq!(
196 (err.file.as_str(), err.line),
197 ("base/data/bad.ron", Some(2))
198 );
199 }
200
201 #[test]
202 fn missing_file_names_the_path() {
203 let err = load_ron::<BTreeMap<String, u8>>(&bundle(), "data/nope.ron")
204 .err()
205 .unwrap_or_else(|| ContentError::new("", "expected an error"));
206 assert_eq!(err.file, "base/data/nope.ron");
207 assert_eq!(err.line, None);
208 assert!(err.message.contains("not found"));
209 }
210}