1use crate::span::is_single_line;
38use crate::{Parse, Source};
39use cfg_if::cfg_if;
40use spanned_json_parser::value::Value as JsonValue;
41use spanned_json_parser::{Position, parse};
42use tanzim_value::{Error, LocatedValue, Location, Map, Value};
43
44#[derive(Clone, Copy, Default)]
63pub struct Json;
64
65impl Json {
66 pub fn new() -> Self {
68 Self
69 }
70}
71
72impl Parse for Json {
73 fn name(&self) -> &str {
74 "JSON"
75 }
76
77 fn supported_format_list(&self) -> Vec<String> {
78 vec!["json".into()]
79 }
80
81 fn parse(&self, src: &Source, bytes: &[u8]) -> Result<LocatedValue, Error> {
82 #[cfg(any(feature = "tracing", feature = "logging"))]
83 let source = src.source();
84 #[cfg(any(feature = "tracing", feature = "logging"))]
85 let resource = src.resource();
86 cfg_if! {
87 if #[cfg(feature = "tracing")] {
88 tracing::debug!(msg = "Parsing JSON configuration", source = source, resource = resource, bytes = bytes.len());
89 } else if #[cfg(feature = "logging")] {
90 log::debug!("msg=\"Parsing JSON configuration\" source={source} resource={resource} bytes={}", bytes.len());
91 }
92 }
93 let text = match std::str::from_utf8(bytes) {
94 Ok(value) => value,
95 Err(_) => {
96 return Err(Error::InvalidUtf8 {
97 location: Box::new(Location::in_source(src.clone(), None, None, None)),
98 });
99 }
100 };
101 let single_line = is_single_line(bytes);
102 let parsed = match parse(text) {
103 Ok(value) => value,
104 Err(error) => {
105 return Err(Error::Parse {
106 text: text.to_string(),
107 location: Some(Box::new(location_from_position(
108 src,
109 single_line,
110 &error.start,
111 Some(&error.end),
112 ))),
113 message: format!("{:?}", error.kind),
114 });
115 }
116 };
117 let location = location_from_position(src, single_line, &parsed.start, Some(&parsed.end));
118 let result = convert_value(
119 src,
120 text,
121 single_line,
122 parsed.value,
123 &parsed.start,
124 location,
125 );
126 if result.is_ok() {
127 cfg_if! {
128 if #[cfg(feature = "tracing")] {
129 tracing::trace!(msg = "Parsed JSON configuration", source = source, resource = resource);
130 } else if #[cfg(feature = "logging")] {
131 log::trace!("msg=\"Parsed JSON configuration\" source={source} resource={resource}");
132 }
133 }
134 }
135 result
136 }
137
138 fn is_format_supported(&self, bytes: &[u8]) -> Option<bool> {
139 match std::str::from_utf8(bytes) {
140 Ok(text) => Some(parse(text).is_ok()),
141 Err(_) => Some(false),
142 }
143 }
144}
145
146pub fn unparse<V: AsRef<Value>>(
166 _source: &Source,
167 value: V,
168) -> Result<String, Box<dyn std::error::Error + Send + Sync + 'static>> {
169 let mut out = String::new();
170 write_json(&mut out, value.as_ref(), 0)?;
171 Ok(out)
172}
173
174fn write_json(
175 out: &mut String,
176 value: &Value,
177 indent: usize,
178) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
179 match value {
180 Value::Bool(value) => out.push_str(if *value { "true" } else { "false" }),
181 Value::Int(value) => out.push_str(&value.to_string()),
182 Value::Float(value) => {
183 if !value.is_finite() {
184 return Err(format!("cannot serialize non-finite float {value} as JSON").into());
185 }
186 out.push_str(&format!("{value:?}"));
187 }
188 Value::String(value) => write_json_string(out, value),
189 Value::List(values) => {
190 if values.is_empty() {
191 out.push_str("[]");
192 return Ok(());
193 }
194 out.push_str("[\n");
195 for (index, item) in values.iter().enumerate() {
196 push_indent(out, indent + 1);
197 write_json(out, item.value(), indent + 1)?;
198 if index + 1 < values.len() {
199 out.push(',');
200 }
201 out.push('\n');
202 }
203 push_indent(out, indent);
204 out.push(']');
205 }
206 Value::Map(map) => {
207 let entries = map.entries();
208 if entries.is_empty() {
209 out.push_str("{}");
210 return Ok(());
211 }
212 out.push_str("{\n");
213 for (index, (key, item)) in entries.iter().enumerate() {
214 push_indent(out, indent + 1);
215 write_json_string(out, key);
216 out.push_str(": ");
217 write_json(out, item.value(), indent + 1)?;
218 if index + 1 < entries.len() {
219 out.push(',');
220 }
221 out.push('\n');
222 }
223 push_indent(out, indent);
224 out.push('}');
225 }
226 Value::Null => out.push_str("null"),
227 }
228 Ok(())
229}
230
231fn push_indent(out: &mut String, indent: usize) {
232 for _ in 0..indent {
233 out.push_str(" ");
234 }
235}
236
237fn write_json_string(out: &mut String, value: &str) {
238 out.push('"');
239 for ch in value.chars() {
240 match ch {
241 '"' => out.push_str("\\\""),
242 '\\' => out.push_str("\\\\"),
243 '\n' => out.push_str("\\n"),
244 '\r' => out.push_str("\\r"),
245 '\t' => out.push_str("\\t"),
246 control if (control as u32) < 0x20 => {
247 out.push_str(&format!("\\u{:04x}", control as u32));
248 }
249 other => out.push(other),
250 }
251 }
252 out.push('"');
253}
254
255fn convert_value(
256 source: &Source,
257 _text: &str,
258 single_line: bool,
259 value: JsonValue,
260 _start: &Position,
261 location: Location,
262) -> Result<LocatedValue, Error> {
263 match value {
264 JsonValue::Null => Ok(LocatedValue::new(Value::Null, location)),
265 JsonValue::Bool(value) => Ok(LocatedValue::new(Value::Bool(value), location)),
266 JsonValue::Number(number) => match number {
267 spanned_json_parser::value::Number::PosInt(value) => {
268 Ok(LocatedValue::new(Value::Int(value as isize), location))
269 }
270 spanned_json_parser::value::Number::NegInt(value) => {
271 Ok(LocatedValue::new(Value::Int(value as isize), location))
272 }
273 spanned_json_parser::value::Number::Float(value) => {
274 Ok(LocatedValue::new(Value::Float(value), location))
275 }
276 },
277 JsonValue::String(value) => Ok(LocatedValue::new(Value::String(value), location)),
278 JsonValue::Array(values) => {
279 let mut list = Vec::new();
280 for item in &values {
281 let item_location =
282 location_from_position(source, single_line, &item.start, Some(&item.end));
283 let converted = convert_value(
284 source,
285 _text,
286 single_line,
287 item.value.clone(),
288 &item.start,
289 item_location,
290 )?;
291 list.push(converted);
292 }
293 Ok(LocatedValue::new(Value::List(list), location))
294 }
295 JsonValue::Object(values) => {
296 let mut map = Map::new();
297 for (key, item) in values {
298 let item_location =
299 location_from_position(source, single_line, &item.start, Some(&item.end));
300 let converted = convert_value(
301 source,
302 _text,
303 single_line,
304 item.value.clone(),
305 &item.start,
306 item_location,
307 )?;
308 map.insert(key, converted);
309 }
310 Ok(LocatedValue::new(Value::Map(map), location))
311 }
312 }
313}
314
315fn location_from_position(
316 source: &Source,
317 single_line: bool,
318 start: &Position,
319 end: Option<&Position>,
320) -> Location {
321 if single_line {
322 return Location::in_source(source.clone(), None, None, None);
323 }
324 let mut length = None;
325 if let Some(end) = end
326 && start.line == end.line
327 && end.col >= start.col
328 {
329 length = Some(end.col - start.col + 1);
330 }
331 Location::in_source(source.clone(), Some(start.line), Some(start.col), length)
332}
333
334#[cfg(all(test, feature = "json"))]
335mod tests {
336 use super::*;
337 use tanzim_source::SourceBuilder;
338
339 fn file_source(resource: &str) -> Source {
340 SourceBuilder::new()
341 .with_source("file")
342 .with_resource(resource)
343 .build()
344 .unwrap()
345 }
346
347 fn loc(value: Value) -> LocatedValue {
348 LocatedValue::new(value, Location::at("file", "test", None, None, None))
349 }
350
351 #[test]
352 fn unparses_complex_json() {
353 let mut nested = Map::new();
354 nested.insert("key".into(), loc(Value::String("va\"lue".into())));
355 let mut map = Map::new();
356 map.insert("name".into(), loc(Value::String("tanzim".into())));
357 map.insert("port".into(), loc(Value::Int(8080)));
358 map.insert("ratio".into(), loc(Value::Float(0.5)));
359 map.insert("debug".into(), loc(Value::Bool(true)));
360 map.insert(
361 "tags".into(),
362 loc(Value::List(vec![
363 loc(Value::String("a".into())),
364 loc(Value::String("b".into())),
365 ])),
366 );
367 map.insert("nested".into(), loc(Value::Map(nested)));
368
369 let text = unparse(&file_source("out.json"), Value::Map(map)).unwrap();
370 assert_eq!(
371 text,
372 "{\n \"name\": \"tanzim\",\n \"port\": 8080,\n \"ratio\": 0.5,\n \"debug\": true,\n \"tags\": [\n \"a\",\n \"b\"\n ],\n \"nested\": {\n \"key\": \"va\\\"lue\"\n }\n}"
373 );
374 }
375
376 #[test]
377 fn parses_json_object() {
378 let parsed = Json::new()
379 .parse(&file_source("config.json"), br#"{"hello":"world"}"#)
380 .unwrap();
381 assert_eq!(
382 parsed
383 .value()
384 .as_map()
385 .unwrap()
386 .get("hello")
387 .unwrap()
388 .value()
389 .as_string()
390 .unwrap(),
391 "world"
392 );
393 }
394
395 #[test]
396 fn detects_json_format() {
397 let parser = Json::new();
398 assert_eq!(parser.is_format_supported(br#"{"a":1}"#), Some(true));
399 assert_eq!(parser.is_format_supported(b"not json"), Some(false));
400 }
401
402 #[test]
403 fn single_line_json_omits_position() {
404 let root = Json::new()
405 .parse(&file_source("a.json"), br#"{"a":1}"#)
406 .unwrap();
407 let map = root.value().as_map().unwrap();
408 let entry = map.get("a").unwrap();
409 assert_eq!(entry.location().line, None);
410 assert_eq!(entry.location().column, None);
411 }
412
413 #[test]
414 fn parses_null() {
415 let root = Json::new()
416 .parse(&file_source("a.json"), b"{\n \"a\": null\n}")
417 .unwrap();
418 let map = root.value().as_map().unwrap();
419 let entry = map.get("a").unwrap();
420 assert!(entry.value().is_null());
421 assert_eq!(entry.location().line, std::num::NonZeroU32::new(2));
422 }
423
424 #[test]
425 fn syntax_error_has_location() {
426 let error = Json::new()
427 .parse(&file_source("a.json"), b"{\n \"a\":\n}\n")
428 .unwrap_err();
429 if let Error::Parse { ref location, .. } = error {
430 let location = location.as_ref().expect("syntax error location");
431 assert!(location.line.is_some());
432 assert!(location.column.is_some());
433 } else {
434 panic!("expected parse error");
435 }
436 let message = format!("{error:#}");
437 assert!(message.contains('^'));
438 }
439}