Skip to main content

hl7_2/
json.rs

1//! A small hand-written JSON reader, used to load dictionaries.
2//!
3//! Dictionaries — the bundled ones in `schemas/` and the ones callers write
4//! themselves for schema mode — are JSON, so something has to read JSON.
5//! Doing it here, in about the space a dependency declaration would take,
6//! keeps this crate's runtime dependency list at exactly one entry
7//! (`er7`, which itself has none), which is worth more in a domain where
8//! dependency trees get audited than the few hundred lines it costs. The
9//! sibling `hl7-2-from-er7-into-json` crate hand-rolls its JSON *writer*
10//! for the same reason; this is the mirror of it.
11//!
12//! The reader is deliberately plain: it accepts RFC 8259 JSON, keeps object
13//! members in file order (dictionaries are read, diffed, and eyeballed by
14//! people, so order is worth preserving), and reports the byte offset of a
15//! syntax error so a typo in a 1,300-line dictionary is findable.
16
17use std::fmt;
18
19/// A parsed JSON value.
20///
21/// Object members are a `Vec` rather than a map so that document order
22/// survives; dictionaries are small enough that lookup by scan is not worth
23/// a `BTreeMap`'s allocation per object.
24#[derive(Debug, Clone, PartialEq)]
25pub enum Value {
26    /// `null`.
27    Null,
28    /// `true` or `false`.
29    Bool(bool),
30    /// Any JSON number, held as `f64`.
31    Number(f64),
32    /// A string, with escape sequences already resolved.
33    String(String),
34    /// An array, in order.
35    Array(Vec<Value>),
36    /// An object: members in the order they appeared.
37    Object(Vec<(String, Value)>),
38}
39
40impl Value {
41    /// The member named `key`, if this is an object that has one.
42    ///
43    /// A duplicate key returns the first occurrence, matching the "first
44    /// wins" reading most JSON tooling settles on.
45    #[must_use]
46    pub fn get(&self, key: &str) -> Option<&Value> {
47        match self {
48            Value::Object(members) => members.iter().find(|(k, _)| k == key).map(|(_, v)| v),
49            _ => None,
50        }
51    }
52
53    /// The string, if this is a string.
54    #[must_use]
55    pub fn as_str(&self) -> Option<&str> {
56        match self {
57            Value::String(s) => Some(s),
58            _ => None,
59        }
60    }
61
62    /// The members, if this is an object.
63    #[must_use]
64    pub fn as_object(&self) -> Option<&[(String, Value)]> {
65        match self {
66            Value::Object(members) => Some(members),
67            _ => None,
68        }
69    }
70
71    /// The elements, if this is an array.
72    #[must_use]
73    pub fn as_array(&self) -> Option<&[Value]> {
74        match self {
75            Value::Array(items) => Some(items),
76            _ => None,
77        }
78    }
79
80    /// The boolean, if this is one.
81    #[must_use]
82    pub fn as_bool(&self) -> Option<bool> {
83        match self {
84            Value::Bool(b) => Some(*b),
85            _ => None,
86        }
87    }
88
89    /// True when this is `null`.
90    #[must_use]
91    pub fn is_null(&self) -> bool {
92        matches!(self, Value::Null)
93    }
94
95    /// The name of this value's kind, for error messages.
96    #[must_use]
97    pub fn kind(&self) -> &'static str {
98        match self {
99            Value::Null => "null",
100            Value::Bool(_) => "boolean",
101            Value::Number(_) => "number",
102            Value::String(_) => "string",
103            Value::Array(_) => "array",
104            Value::Object(_) => "object",
105        }
106    }
107}
108
109/// A JSON syntax error: what was wrong, and where.
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct Error {
112    /// What the reader expected, or what it found instead.
113    pub detail: String,
114    /// Byte offset into the input where the problem was noticed.
115    pub offset: usize,
116}
117
118impl fmt::Display for Error {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(f, "invalid JSON at byte {}: {}", self.offset, self.detail)
121    }
122}
123
124impl std::error::Error for Error {}
125
126/// Read a complete JSON document. Trailing content after the top-level
127/// value is an error, so a truncated or double-pasted file is caught here
128/// rather than silently half-read.
129/// # Errors
130///
131/// [`Error`] when the text is not valid JSON, with where parsing gave up.
132pub fn parse(text: &str) -> Result<Value, Error> {
133    let mut reader = Reader {
134        bytes: text.as_bytes(),
135        pos: 0,
136        depth: 0,
137    };
138    reader.skip_whitespace();
139    let value = reader.value()?;
140    reader.skip_whitespace();
141    if reader.pos != reader.bytes.len() {
142        return Err(reader.error("unexpected trailing content"));
143    }
144    Ok(value)
145}
146
147/// How deeply objects and arrays may nest before reading gives up.
148///
149/// Reading is recursive, so nesting depth is stack depth: without a limit a
150/// few kilobytes of `[[[[…` abort the process with a stack overflow, which
151/// a library must never do to a caller who merely read a file from
152/// somewhere. A dictionary nests a handful of levels, so this is far above
153/// anything real and far below what threatens the stack.
154const MAX_DEPTH: usize = 256;
155
156struct Reader<'a> {
157    bytes: &'a [u8],
158    pos: usize,
159    depth: usize,
160}
161
162impl Reader<'_> {
163    fn error(&self, detail: &str) -> Error {
164        Error {
165            detail: detail.to_string(),
166            offset: self.pos,
167        }
168    }
169
170    fn peek(&self) -> Option<u8> {
171        self.bytes.get(self.pos).copied()
172    }
173
174    fn skip_whitespace(&mut self) {
175        while matches!(self.peek(), Some(b' ' | b'\t' | b'\r' | b'\n')) {
176            self.pos += 1;
177        }
178    }
179
180    /// Consume `word` if it is next, reporting a useful error if not.
181    fn literal(&mut self, word: &str, value: Value) -> Result<Value, Error> {
182        if self.bytes[self.pos..].starts_with(word.as_bytes()) {
183            self.pos += word.len();
184            Ok(value)
185        } else {
186            Err(self.error(&format!("expected `{word}`")))
187        }
188    }
189
190    fn value(&mut self) -> Result<Value, Error> {
191        match self.peek() {
192            None => Err(self.error("expected a value")),
193            Some(b'{') => self.nested(Reader::object),
194            Some(b'[') => self.nested(Reader::array),
195            Some(b'"') => Ok(Value::String(self.string()?)),
196            Some(b't') => self.literal("true", Value::Bool(true)),
197            Some(b'f') => self.literal("false", Value::Bool(false)),
198            Some(b'n') => self.literal("null", Value::Null),
199            Some(_) => self.number(),
200        }
201    }
202
203    /// Read one container, counting the level so that nesting cannot run
204    /// the stack out from under the caller.
205    fn nested(&mut self, read: fn(&mut Self) -> Result<Value, Error>) -> Result<Value, Error> {
206        if self.depth >= MAX_DEPTH {
207            return Err(self.error(&format!("nested more than {MAX_DEPTH} deep")));
208        }
209        self.depth += 1;
210        let value = read(self);
211        self.depth -= 1;
212        value
213    }
214
215    fn object(&mut self) -> Result<Value, Error> {
216        self.pos += 1; // `{`
217        let mut members = Vec::new();
218        self.skip_whitespace();
219        if self.peek() == Some(b'}') {
220            self.pos += 1;
221            return Ok(Value::Object(members));
222        }
223        loop {
224            self.skip_whitespace();
225            if self.peek() != Some(b'"') {
226                return Err(self.error("expected a member name"));
227            }
228            let name = self.string()?;
229            self.skip_whitespace();
230            if self.peek() != Some(b':') {
231                return Err(self.error("expected `:` after a member name"));
232            }
233            self.pos += 1;
234            self.skip_whitespace();
235            members.push((name, self.value()?));
236            self.skip_whitespace();
237            match self.peek() {
238                Some(b',') => self.pos += 1,
239                Some(b'}') => {
240                    self.pos += 1;
241                    return Ok(Value::Object(members));
242                }
243                _ => return Err(self.error("expected `,` or `}`")),
244            }
245        }
246    }
247
248    fn array(&mut self) -> Result<Value, Error> {
249        self.pos += 1; // `[`
250        let mut items = Vec::new();
251        self.skip_whitespace();
252        if self.peek() == Some(b']') {
253            self.pos += 1;
254            return Ok(Value::Array(items));
255        }
256        loop {
257            self.skip_whitespace();
258            items.push(self.value()?);
259            self.skip_whitespace();
260            match self.peek() {
261                Some(b',') => self.pos += 1,
262                Some(b']') => {
263                    self.pos += 1;
264                    return Ok(Value::Array(items));
265                }
266                _ => return Err(self.error("expected `,` or `]`")),
267            }
268        }
269    }
270
271    fn string(&mut self) -> Result<String, Error> {
272        self.pos += 1; // opening quote
273        let mut out = String::new();
274        loop {
275            let Some(byte) = self.peek() else {
276                return Err(self.error("unterminated string"));
277            };
278            match byte {
279                b'"' => {
280                    self.pos += 1;
281                    return Ok(out);
282                }
283                b'\\' => {
284                    self.pos += 1;
285                    self.escape(&mut out)?;
286                }
287                0x00..=0x1f => return Err(self.error("unescaped control character in string")),
288                _ => {
289                    // Multi-byte UTF-8 passes through whole: find the end of
290                    // this character in the original text rather than
291                    // pushing bytes one at a time.
292                    let start = self.pos;
293                    self.pos += 1;
294                    while matches!(self.peek(), Some(b) if b & 0xc0 == 0x80) {
295                        self.pos += 1;
296                    }
297                    match std::str::from_utf8(&self.bytes[start..self.pos]) {
298                        Ok(text) => out.push_str(text),
299                        Err(_) => return Err(self.error("invalid UTF-8 in string")),
300                    }
301                }
302            }
303        }
304    }
305
306    fn escape(&mut self, out: &mut String) -> Result<(), Error> {
307        let Some(byte) = self.peek() else {
308            return Err(self.error("unterminated escape sequence"));
309        };
310        self.pos += 1;
311        out.push(match byte {
312            b'"' => '"',
313            b'\\' => '\\',
314            b'/' => '/',
315            b'b' => '\u{8}',
316            b'f' => '\u{c}',
317            b'n' => '\n',
318            b'r' => '\r',
319            b't' => '\t',
320            b'u' => return self.unicode_escape(out),
321            _ => return Err(self.error("unknown escape sequence")),
322        });
323        Ok(())
324    }
325
326    /// Resolve `\uXXXX`, joining a surrogate pair when one follows.
327    fn unicode_escape(&mut self, out: &mut String) -> Result<(), Error> {
328        let high = self.hex4()?;
329        let scalar = if (0xd800..0xdc00).contains(&high) {
330            if !self.bytes[self.pos..].starts_with(b"\\u") {
331                return Err(self.error("lone high surrogate"));
332            }
333            self.pos += 2;
334            let low = self.hex4()?;
335            if !(0xdc00..0xe000).contains(&low) {
336                return Err(self.error("expected a low surrogate"));
337            }
338            0x10000 + ((high - 0xd800) << 10) + (low - 0xdc00)
339        } else if (0xdc00..0xe000).contains(&high) {
340            return Err(self.error("lone low surrogate"));
341        } else {
342            high
343        };
344        match char::from_u32(scalar) {
345            Some(c) => out.push(c),
346            None => return Err(self.error("escape is not a Unicode scalar value")),
347        }
348        Ok(())
349    }
350
351    fn hex4(&mut self) -> Result<u32, Error> {
352        let end = self.pos + 4;
353        if end > self.bytes.len() {
354            return Err(self.error("truncated `\\u` escape"));
355        }
356        let mut value = 0;
357        for &byte in &self.bytes[self.pos..end] {
358            let digit = match byte {
359                b'0'..=b'9' => u32::from(byte - b'0'),
360                b'a'..=b'f' => u32::from(byte - b'a') + 10,
361                b'A'..=b'F' => u32::from(byte - b'A') + 10,
362                _ => return Err(self.error("`\\u` escape needs four hex digits")),
363            };
364            value = value * 16 + digit;
365        }
366        self.pos = end;
367        Ok(value)
368    }
369
370    fn number(&mut self) -> Result<Value, Error> {
371        let start = self.pos;
372        if self.peek() == Some(b'-') {
373            self.pos += 1;
374        }
375        while matches!(
376            self.peek(),
377            Some(b'0'..=b'9' | b'.' | b'e' | b'E' | b'+' | b'-')
378        ) {
379            self.pos += 1;
380        }
381        let text = std::str::from_utf8(&self.bytes[start..self.pos]).unwrap_or("");
382        if let Ok(number) = text.parse::<f64>() {
383            Ok(Value::Number(number))
384        } else {
385            self.pos = start;
386            Err(self.error("expected a value"))
387        }
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn reads_the_shapes_a_dictionary_uses() {
397        let value =
398            parse(r#"{"types": {"XPN": ["FN", "ST"]}, "n": 2.5, "ok": true, "x": null}"#).unwrap();
399        assert_eq!(
400            value
401                .get("types")
402                .unwrap()
403                .get("XPN")
404                .unwrap()
405                .as_array()
406                .unwrap()[0]
407                .as_str(),
408            Some("FN")
409        );
410        assert_eq!(value.get("n"), Some(&Value::Number(2.5)));
411        assert_eq!(value.get("ok").unwrap().as_bool(), Some(true));
412        assert!(value.get("x").unwrap().is_null());
413        assert_eq!(value.get("missing"), None);
414    }
415
416    #[test]
417    fn keeps_object_order() {
418        let value = parse(r#"{"b": 1, "a": 2}"#).unwrap();
419        let names: Vec<&str> = value
420            .as_object()
421            .unwrap()
422            .iter()
423            .map(|(name, _)| name.as_str())
424            .collect();
425        assert_eq!(names, ["b", "a"]);
426    }
427
428    #[test]
429    fn resolves_escapes_including_surrogate_pairs() {
430        assert_eq!(
431            parse(r#""aé😀\n\"\\""#).unwrap().as_str(),
432            Some("aé😀\n\"\\")
433        );
434        assert_eq!(parse("\"caf\u{e9}\"").unwrap().as_str(), Some("café"));
435    }
436
437    #[test]
438    fn reports_where_the_problem_is() {
439        // A dictionary is long; "somewhere" is not a useful answer.
440        let error = parse(r#"{"a": 1,}"#).unwrap_err();
441        assert_eq!(error.offset, 8);
442        assert!(error.to_string().contains("byte 8"), "{error}");
443        assert!(parse(r#"{"a": 1} {"b": 2}"#).is_err());
444        assert!(parse(r#"{"a" 1}"#).is_err());
445        assert!(parse(r#""unterminated"#).is_err());
446        assert!(parse("").is_err());
447    }
448
449    #[test]
450    fn nesting_past_the_limit_is_an_error_not_a_stack_overflow() {
451        // Reading is recursive, so nesting depth is stack depth. Without a
452        // limit these aborted the process — a crash the caller cannot catch,
453        // from a dictionary file they merely read off disk.
454        let deep_objects = format!("{}1{}", "{\"a\":".repeat(1000), "}".repeat(1000));
455        let deep_arrays = format!("{}1{}", "[".repeat(1000), "]".repeat(1000));
456        for text in [deep_objects, deep_arrays] {
457            let error = parse(&text).unwrap_err();
458            assert!(error.detail.contains("nested more than"), "{error}");
459        }
460    }
461
462    #[test]
463    fn ordinary_nesting_depth_still_reads() {
464        let depth = 64;
465        let text = format!("{}1{}", "[".repeat(depth), "]".repeat(depth));
466        assert!(parse(&text).is_ok());
467    }
468}