1use crate::Deserialize;
20use crate::span::is_single_line;
21use cfg_if::cfg_if;
22use spanned_json_parser::value::Value as JsonValue;
23use spanned_json_parser::{Position, parse};
24use tanzim_value::{Error, LocatedValue, Location, Map, Value};
25
26#[derive(Clone, Copy, Default)]
27pub struct Json;
28
29impl Json {
30 pub fn new() -> Self {
31 Self
32 }
33}
34
35impl Deserialize for Json {
36 fn name(&self) -> &str {
37 "JSON"
38 }
39
40 fn supported_format_list(&self) -> Vec<String> {
41 vec!["json".into()]
42 }
43
44 fn parse(&self, source: &str, resource: &str, bytes: &[u8]) -> Result<LocatedValue, Error> {
45 cfg_if! {
46 if #[cfg(feature = "tracing")] {
47 tracing::debug!(msg = "Parsing JSON configuration", source = source, resource = resource, bytes = bytes.len());
48 } else if #[cfg(feature = "logging")] {
49 log::debug!("msg=\"Parsing JSON configuration\" source={source} resource={resource} bytes={}", bytes.len());
50 }
51 }
52 let text = match std::str::from_utf8(bytes) {
53 Ok(value) => value,
54 Err(_) => {
55 return Err(Error::InvalidUtf8 {
56 location: Location::at(source, resource, None, None, None),
57 });
58 }
59 };
60 let single_line = is_single_line(bytes);
61 let parsed = match parse(text) {
62 Ok(value) => value,
63 Err(error) => {
64 return Err(Error::Parse {
65 text: text.to_string(),
66 location: Some(location_from_position(
67 source,
68 resource,
69 single_line,
70 &error.start,
71 Some(&error.end),
72 )),
73 message: format!("{:?}", error.kind),
74 });
75 }
76 };
77 let location = location_from_position(
78 source,
79 resource,
80 single_line,
81 &parsed.start,
82 Some(&parsed.end),
83 );
84 let result = convert_value(
85 source,
86 resource,
87 text,
88 single_line,
89 parsed.value,
90 &parsed.start,
91 location,
92 );
93 if result.is_ok() {
94 cfg_if! {
95 if #[cfg(feature = "tracing")] {
96 tracing::trace!(msg = "Parsed JSON configuration", source = source, resource = resource);
97 } else if #[cfg(feature = "logging")] {
98 log::trace!("msg=\"Parsed JSON configuration\" source={source} resource={resource}");
99 }
100 }
101 }
102 result
103 }
104
105 fn is_format_supported(&self, bytes: &[u8]) -> Option<bool> {
106 match std::str::from_utf8(bytes) {
107 Ok(text) => Some(parse(text).is_ok()),
108 Err(_) => Some(false),
109 }
110 }
111}
112
113fn convert_value(
114 source: &str,
115 resource: &str,
116 text: &str,
117 single_line: bool,
118 value: JsonValue,
119 _start: &Position,
120 location: Location,
121) -> Result<LocatedValue, Error> {
122 match value {
123 JsonValue::Null => Err(Error::UnsupportedNull {
124 text: text.to_string(),
125 location,
126 }),
127 JsonValue::Bool(value) => Ok(LocatedValue {
128 value: Value::Bool(value),
129 location,
130 }),
131 JsonValue::Number(number) => match number {
132 spanned_json_parser::value::Number::PosInt(value) => Ok(LocatedValue {
133 value: Value::Int(value as isize),
134 location,
135 }),
136 spanned_json_parser::value::Number::NegInt(value) => Ok(LocatedValue {
137 value: Value::Int(value as isize),
138 location,
139 }),
140 spanned_json_parser::value::Number::Float(value) => Ok(LocatedValue {
141 value: Value::Float(value),
142 location,
143 }),
144 },
145 JsonValue::String(value) => Ok(LocatedValue {
146 value: Value::String(value),
147 location,
148 }),
149 JsonValue::Array(values) => {
150 let mut list = Vec::new();
151 for item in &values {
152 let item_location = location_from_position(
153 source,
154 resource,
155 single_line,
156 &item.start,
157 Some(&item.end),
158 );
159 let converted = convert_value(
160 source,
161 resource,
162 text,
163 single_line,
164 item.value.clone(),
165 &item.start,
166 item_location,
167 )?;
168 list.push(converted);
169 }
170 Ok(LocatedValue {
171 value: Value::List(list),
172 location,
173 })
174 }
175 JsonValue::Object(values) => {
176 let mut map = Map::new();
177 for (key, item) in values {
178 let item_location = location_from_position(
179 source,
180 resource,
181 single_line,
182 &item.start,
183 Some(&item.end),
184 );
185 let converted = convert_value(
186 source,
187 resource,
188 text,
189 single_line,
190 item.value.clone(),
191 &item.start,
192 item_location,
193 )?;
194 map.insert(key, converted);
195 }
196 Ok(LocatedValue {
197 value: Value::Map(map),
198 location,
199 })
200 }
201 }
202}
203
204fn location_from_position(
205 source: &str,
206 resource: &str,
207 single_line: bool,
208 start: &Position,
209 end: Option<&Position>,
210) -> Location {
211 if single_line {
212 return Location::at(source, resource, None, None, None);
213 }
214 let mut length = None;
215 if let Some(end) = end
216 && start.line == end.line
217 && end.col >= start.col
218 {
219 length = Some(end.col - start.col + 1);
220 }
221 Location::at(source, resource, Some(start.line), Some(start.col), length)
222}
223
224#[cfg(all(test, feature = "json"))]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn parses_json_object() {
230 let parsed = Json::new()
231 .parse("file", "config.json", br#"{"hello":"world"}"#)
232 .unwrap();
233 assert_eq!(
234 parsed
235 .value
236 .as_map()
237 .unwrap()
238 .get("hello")
239 .unwrap()
240 .value
241 .as_string()
242 .unwrap(),
243 "world"
244 );
245 }
246
247 #[test]
248 fn detects_json_format() {
249 let parser = Json::new();
250 assert_eq!(parser.is_format_supported(br#"{"a":1}"#), Some(true));
251 assert_eq!(parser.is_format_supported(b"not json"), Some(false));
252 }
253
254 #[test]
255 fn single_line_json_omits_position() {
256 let root = Json::new().parse("file", "a.json", br#"{"a":1}"#).unwrap();
257 let map = root.value.as_map().unwrap();
258 let entry = map.get("a").unwrap();
259 assert_eq!(entry.location.line, None);
260 assert_eq!(entry.location.column, None);
261 }
262
263 #[test]
264 fn rejects_null() {
265 let error = Json::new()
266 .parse("file", "a.json", b"{\n \"a\": null\n}")
267 .unwrap_err();
268 assert!(matches!(error, Error::UnsupportedNull { .. }));
269 let message = format!("{error:#}");
270 assert!(message.contains('^'));
271 assert!(message.contains("null"));
272 }
273
274 #[test]
275 fn syntax_error_has_location() {
276 let error = Json::new()
277 .parse("file", "a.json", b"{\n \"a\":\n}\n")
278 .unwrap_err();
279 if let Error::Parse { ref location, .. } = error {
280 let location = location.as_ref().expect("syntax error location");
281 assert!(location.line.is_some());
282 assert!(location.column.is_some());
283 } else {
284 panic!("expected parse error");
285 }
286 let message = format!("{error:#}");
287 assert!(message.contains('^'));
288 }
289}