Skip to main content

ocpi_tariffs/
json.rs

1//! JSON parsing and typed decoding for the OCPI CDR pricing/generating and CDR/Tariff linting pipeline.
2//!
3//! # Parsing vs decoding
4//!
5//! Parsing and decoding are intentionally separated so the linter can emit
6//! actionable warnings rather than hard parse errors.
7//!
8//! **Parsing** ([`parser`], [`parse`]) converts a raw JSON `&str` into an
9//! [`Element`] tree. The parser is deliberately lenient about string content:
10//! it only verifies structural correctness (balanced delimiters, valid
11//! top-level values) and leaves escape sequences and control characters
12//! untouched inside [`RawStr`].
13//!
14//! **Decoding** ([`decode`]) interprets the raw JSON String as a `&str`.
15//! Calling [`RawStr::decode_escapes`] validates escape sequences and
16//! rejects control characters, returning [`decode::Warning`]
17//! values instead of hard errors. This lets the linter pinpoint the exact
18//! field, report what is wrong, and suggest a corrected encoding.
19//! The `price` and `generate` mods can choose to hard fail on specific `Warning`s.
20//!
21pub mod decode;
22mod parser;
23pub(crate) mod walk;
24pub mod write;
25
26#[cfg(test)]
27pub(crate) mod test;
28
29#[cfg(test)]
30mod test_line_col;
31
32#[cfg(test)]
33mod test_path;
34
35#[cfg(test)]
36mod test_path_matches_glob;
37
38#[cfg(test)]
39mod test_source_json;
40
41use std::{
42    borrow::{Borrow, Cow},
43    collections::{btree_set, BTreeMap, BTreeSet},
44    fmt::{self, Write as _},
45    rc::Rc,
46};
47
48use crate::warning::{Caveat, IntoCaveat, Set, Verdict};
49use crate::{
50    string,
51    warning::{self, CaveatDeferred},
52};
53
54pub(crate) use parser::parse;
55pub use parser::{Error, ErrorKind as ParseErrorKind};
56
57/// Parse a raw JSON `&str` into a [`Document`] and require the root value to be a JSON object.
58///
59/// The input size is gated by [`string::ReasonableLen`]; oversized input returns
60/// [`ParseError::SizeExceedsMax`].
61pub fn parse_object(json: &str) -> Result<Document<'_>, ParseError> {
62    let json = string::ReasonableLen::new(json).map_err(|_e| ParseError::SizeExceedsMax)?;
63    let doc = parse(json).map_err(ParseError::Json)?;
64
65    if !doc.root().is_object() {
66        return Err(ParseError::ShouldBeAnObject);
67    }
68
69    Ok(doc)
70}
71
72#[derive(Debug)]
73pub enum ParseError {
74    /// The JSON parser was unable to parse the JSON str.
75    Json(Error),
76
77    /// The OCPI object should be a JSON object.
78    ShouldBeAnObject,
79
80    /// The size of the input `str` exceeds the maximum deemed reasonable.
81    SizeExceedsMax,
82}
83
84impl fmt::Display for ParseError {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        match self {
87            Self::Json(error) => write!(f, "{error}"),
88            Self::ShouldBeAnObject => f.write_str("The CDR should be an object."),
89            Self::SizeExceedsMax => write!(
90                f,
91                "The input `&str` exceeds the reasonable maximum `{} MB`.",
92                string::ReasonableLen::FACTOR
93            ),
94        }
95    }
96}
97
98impl std::error::Error for ParseError {
99    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
100        match &self {
101            ParseError::Json(err) => Some(err),
102            ParseError::ShouldBeAnObject | ParseError::SizeExceedsMax => None,
103        }
104    }
105}
106
107/// Return the `json::Element` If the given field exists(`Some`).
108/// Otherwise, add a `Warning::FieldRequired` to the set of `Warning`s and return them as an `Err`.
109#[doc(hidden)]
110#[macro_export]
111macro_rules! required_field_or_bail {
112    ($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {
113        match $fields.get($field_name) {
114            Some(field_elem) => field_elem,
115            None => {
116                return $warnings.bail(
117                    $elem,
118                    Warning::FieldRequired {
119                        field_name: $field_name.into(),
120                    },
121                );
122            }
123        }
124    };
125}
126
127/// Return the `json::Element` If the given field exists(`Some`).
128/// Otherwise, add a `Warning::FieldRequired` to the set of `Warning`s and return them as an `Err`.
129#[doc(hidden)]
130#[macro_export]
131macro_rules! required_field {
132    ($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {{
133        let field = $fields.get($field_name);
134
135        if field.is_none() {
136            $warnings.insert(
137                $elem,
138                Warning::FieldRequired {
139                    field_name: $field_name.into(),
140                },
141            );
142        }
143
144        field
145    }};
146}
147
148/// Return the `Field`s of the given `json::Element` if it's a JSON object.
149/// Otherwise, add a `Warning::FieldInvalidType` to the set of `Warning`s and return then as an `Err`.
150#[doc(hidden)]
151#[macro_export]
152macro_rules! expect_object_or_bail {
153    ($elem:expr, $warnings:expr) => {
154        match $elem.as_object_fields() {
155            Some(fields) => fields,
156            None => {
157                return $warnings.bail(
158                    $elem,
159                    Warning::FieldInvalidType {
160                        expected_type: json::ValueKind::Object,
161                    },
162                );
163            }
164        }
165    };
166}
167
168/// Return the `json::Element`s of the given `json::Element` if it's a JSON array.
169/// Otherwise, add a `Warning::FieldInvalidType` to the set of `Warning`s and return then as an `Err`.
170#[doc(hidden)]
171#[macro_export]
172macro_rules! expect_array_or_bail {
173    ($elem:expr, $warnings:expr) => {
174        match $elem.as_array() {
175            Some(fields) => fields,
176            None => {
177                return $warnings.bail(
178                    $elem,
179                    Warning::FieldInvalidType {
180                        expected_type: json::ValueKind::Array,
181                    },
182                );
183            }
184        }
185    };
186}
187
188/// Get a field by name and fail if it doesn't exist.
189///
190/// Convert the value of the field to the `$target` type.
191#[doc(hidden)]
192#[macro_export]
193macro_rules! parse_required_or_bail {
194    ($elem:expr, $fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
195        #[expect(clippy::allow_attributes, reason = "The allow attribute is needed here as the callers scope determines if the imports are used or not")]
196        #[allow(
197            unused,
198            reason = "The macro uses the import but maybe the outside scope does too."
199        )]
200        use $crate::json::FromJson;
201        use $crate::warning::GatherWarnings as _;
202
203        let elem = $crate::required_field_or_bail!($elem, $fields, $elem_name, $warnings);
204        <$target as FromJson>::from_json(elem)?.gather_warnings_into(&mut $warnings)
205    }};
206}
207
208/// Get a field by name and return `None` if it doesn't exist.
209///
210/// Convert the value of the field to the `$target` type.
211#[doc(hidden)]
212#[macro_export]
213macro_rules! parse_required {
214    ($elem:expr, $fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
215        #[expect(
216            clippy::allow_attributes,
217            reason = "The allow attribute is needed here as the callers scope determines if the imports are used or not"
218        )]
219        #[allow(
220            unused,
221            reason = "The macro uses the import but maybe the outside scope does too."
222        )]
223        use $crate::json::FromJson;
224        use $crate::warning::GatherWarnings as _;
225
226        let elem = $crate::required_field!($elem, $fields, $elem_name, $warnings);
227
228        if let Some(elem) = elem {
229            let value =
230                <$target as FromJson>::from_json(elem)?.gather_warnings_into(&mut $warnings);
231            Some(value)
232        } else {
233            None
234        }
235    }};
236}
237
238/// Get an optional field by name and convert the field value to the `$target` type using the
239/// blanket impl `Option<$target>` that can handle `null` JSON values.
240#[doc(hidden)]
241#[macro_export]
242macro_rules! parse_nullable_or_bail {
243    ($fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
244        #[expect(
245            clippy::allow_attributes,
246            reason = "The allow attribute is needed here as the callers scope determines if the imports are used or not"
247        )]
248        #[allow(
249            unused,
250            reason = "The macro uses the import but maybe the outside scope does too."
251        )]
252        use $crate::json::FromJson as _;
253        use $crate::warning::GatherWarnings as _;
254
255        match $fields.get($elem_name) {
256            Some(elem) => Option::<$target>::from_json(elem)?.gather_warnings_into(&mut $warnings),
257            None => None,
258        }
259    }};
260}
261
262/// The output of [`parse`]: the element tree with path resolution embedded in each element.
263#[derive(Clone, Debug)]
264pub struct Document<'buf> {
265    /// Shared inner state; also held by every element in the tree.
266    inner: Rc<DocumentInner<'buf>>,
267    /// Root element of the parsed tree.
268    root: Element<'buf>,
269}
270
271impl<'buf> Document<'buf> {
272    /// Returns the source JSON string this document was parsed from.
273    pub fn source(&self) -> &'buf str {
274        self.inner.source
275    }
276
277    /// Returns the root element of this document.
278    pub fn root(&self) -> &Element<'buf> {
279        &self.root
280    }
281}
282
283/// A JSON [`Element`] with identity, source span, and value.
284///
285/// Each element carries a shared reference to the document it was parsed from,
286/// so [`Element::path()`] can resolve its path.
287#[derive(Clone, Debug)]
288pub struct Element<'buf> {
289    /// Shared document state.
290    doc: Rc<DocumentInner<'buf>>,
291    /// Unique identifier within the document; sequentially assigned depth-first.
292    id: ElemId,
293    /// Byte range of the value only; use for replacement edits.
294    span: Span,
295    /// End of the value plus any trailing comma and whitespace; use for removal edits.
296    /// Equal to `span.end` when there is no trailing comma (root element, or last sibling).
297    full_span_end: u32,
298    /// Parsed value, borrowing from the source `&str`.
299    value: Value<'buf>,
300}
301
302impl PartialEq for Element<'_> {
303    fn eq(&self, other: &Self) -> bool {
304        self.id == other.id
305            && self.span == other.span
306            && self.full_span_end == other.full_span_end
307            && self.value == other.value
308    }
309}
310
311impl Eq for Element<'_> {}
312
313impl<'buf> Element<'buf> {
314    pub fn id(&self) -> ElemId {
315        self.id
316    }
317
318    pub fn span(&self) -> Span {
319        self.span
320    }
321
322    /// Returns the span covering the value plus any trailing comma and whitespace.
323    ///
324    /// Choose the removal span based on the element's position in its parent:
325    ///
326    /// | Case | Span to erase |
327    /// |---|---|
328    /// | Replace value | [`Element::span`] |
329    /// | Remove non-last item | `element.full_span()` |
330    /// | Remove last item (siblings exist) | `siblings[i-1].span().end .. element.span().end` |
331    /// | Remove only item | `element.span()` |
332    ///
333    /// When there is no trailing comma (root element or last sibling),
334    /// `full_span() == span()`. Erasing `full_span` of a last item leaves a
335    /// dangling comma on the previous sibling; use the predecessor's `span().end`
336    /// as the start of the removal range instead.
337    pub fn full_span(&self) -> Span {
338        Span {
339            start: self.span.start,
340            end: self.full_span_end,
341        }
342    }
343
344    pub fn value(&self) -> &Value<'buf> {
345        &self.value
346    }
347
348    /// Returns the RFC 9535 path to this element.
349    ///
350    /// NOTE: The `Path` is constructed anew every time this functions is called.
351    pub fn path(&self) -> Path {
352        self.doc.paths.path_of(self)
353    }
354
355    /// Returns the slice of the source JSON that this element spans.
356    #[expect(
357        clippy::string_slice,
358        reason = "spans are produced by the parser from the same source, so slices are always valid"
359    )]
360    #[expect(
361        clippy::as_conversions,
362        reason = "The index is guaranteed within bounds by the parser"
363    )]
364    pub fn source_json_value(&self) -> &'buf str {
365        &self.doc.source[self.span.start as usize..self.span.end as usize]
366    }
367
368    /// The full source string; all element spans are relative to this.
369    pub fn source(&self) -> &'buf str {
370        self.doc.source
371    }
372
373    /// Return the location that this element begins at.
374    #[expect(
375        clippy::string_slice,
376        reason = "spans are produced by the parser from the same source, so slices are always valid"
377    )]
378    #[expect(
379        clippy::as_conversions,
380        reason = "The index is guaranteed within bounds by the parser"
381    )]
382    pub fn location(&self) -> Location {
383        let source = self.doc.source;
384
385        // Slice up to the start of the span to calculate line and col numbers.
386        let lead_in = &source[..self.span.start as usize];
387        line_col(lead_in)
388    }
389
390    /// Return the inner `Value` by ref.
391    pub fn as_value(&self) -> &Value<'buf> {
392        &self.value
393    }
394
395    /// Return `Some(&str)` if the `Value` is a `String`.
396    pub fn to_raw_str(&self) -> Option<RawStr<'buf>> {
397        self.value.to_raw_str()
398    }
399
400    /// Return `Some(&[Field])` if the `Value` is a `Object`.
401    pub fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
402        self.value.as_object_fields()
403    }
404
405    pub fn as_array(&self) -> Option<&[Element<'buf>]> {
406        self.value.as_array()
407    }
408
409    pub fn as_number_str(&self) -> Option<&str> {
410        self.value.as_number()
411    }
412
413    /// Return true if the `Element`s `Value` is null.
414    pub fn is_null(&self) -> bool {
415        self.value.is_null()
416    }
417
418    /// Return true if the `Element`s `Value` is an object.
419    pub fn is_object(&self) -> bool {
420        self.value.is_object()
421    }
422
423    /// Return true if the `Element`s `Value` is an array.
424    pub fn is_array(&self) -> bool {
425        self.value.is_array()
426    }
427}
428
429/// A JSON value that borrows its content from the source JSON `&str`.
430#[derive(Clone, Debug, Eq, PartialEq)]
431pub enum Value<'buf> {
432    /// JSON `null` literal.
433    Null,
434    /// JSON `true` literal.
435    True,
436    /// JSON `false` literal.
437    False,
438    /// String content with quotes removed; escape sequences are not decoded.
439    String(RawStr<'buf>),
440    /// Raw number text; not guaranteed to fit any specific numeric type.
441    Number(&'buf str),
442    /// Ordered list of child elements.
443    Array(Vec<Element<'buf>>),
444    /// Ordered list of key-value fields.
445    Object(Vec<Field<'buf>>),
446}
447
448impl fmt::Display for Value<'_> {
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        match self {
451            Self::Null => write!(f, "null"),
452            Self::True => write!(f, "true"),
453            Self::False => write!(f, "false"),
454            Self::String(s) => write!(f, "{}", s.as_unescaped_str()),
455            Self::Number(s) => write!(f, "{s}"),
456            Self::Array(..) => f.write_str("[...]"),
457            Self::Object(..) => f.write_str("{...}"),
458        }
459    }
460}
461
462/// Byte range of a JSON token within the source string.
463#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
464pub struct Span {
465    /// Byte offset of the first byte of the token.
466    pub start: u32,
467    /// Byte offset one past the last byte of the token.
468    pub end: u32,
469}
470
471impl Span {
472    fn new(start: u32, end: u32) -> Self {
473        Self { start, end }
474    }
475}
476
477/// A file location expressed as line and column.
478#[derive(Clone, Debug)]
479pub struct Location {
480    /// The line index is 0 based.
481    pub line: u32,
482
483    /// The col index is 0 based.
484    pub col: u32,
485}
486
487impl From<(u32, u32)> for Location {
488    fn from(value: (u32, u32)) -> Self {
489        Self {
490            line: value.0,
491            col: value.1,
492        }
493    }
494}
495
496impl From<Location> for (u32, u32) {
497    fn from(value: Location) -> Self {
498        (value.line, value.col)
499    }
500}
501
502impl PartialEq<(u32, u32)> for Location {
503    fn eq(&self, other: &(u32, u32)) -> bool {
504        self.line == other.0 && self.col == other.1
505    }
506}
507
508impl fmt::Display for Location {
509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510        write!(f, "{}:{}", self.line, self.col)
511    }
512}
513
514/// Return the line and column indices of the end of the slice.
515///
516/// The line and column indices are zero based.
517pub fn line_col(s: &str) -> Location {
518    let mut chars = s.chars().rev();
519    let mut line = 0_u32;
520    let mut col = 0_u32;
521
522    // The col only needs to be calculated on the final line so we iterate from the last char
523    // back to the start of the line and then only continue to count the lines after that.
524    //
525    // This is less work than continuously counting chars from the front of the slice.
526    for c in chars.by_ref() {
527        // If the `&str` is multiline, we count the line and stop accumulating the col count too.
528        if c == '\n' {
529            let Some(n) = line.checked_add(1) else {
530                break;
531            };
532            line = n;
533            break;
534        }
535        let Some(n) = col.checked_add(1) else {
536            break;
537        };
538        col = n;
539    }
540
541    // The col is now known, continue to the start of the str counting newlines as we go.
542    for c in chars {
543        if c == '\n' {
544            let Some(n) = line.checked_add(1) else {
545                break;
546            };
547            line = n;
548        }
549    }
550
551    Location { line, col }
552}
553
554/// Unique sequential index of a JSON [`Element`] within a document.
555///
556/// Assigned depth-first by the parser. `Parser::alloc_id` uses `checked_add`
557/// so the counter never wraps silently — overflow becomes `ParseError::TooLarge`.
558#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
559pub struct ElemId(usize);
560
561/// Records how one element was reached from its parent.
562#[derive(Debug)]
563enum PathEntry<'buf> {
564    /// The root element; has no parent.
565    Root,
566    /// An object field; the element's path ends with a key segment.
567    Field {
568        /// Id of the parent object element.
569        parent: ElemId,
570        /// Key text borrowed from the source JSON, without surrounding quotes.
571        key: RawStr<'buf>,
572    },
573    /// An array item; the element's path ends with an index segment.
574    Item {
575        /// Id of the parent array element.
576        parent: ElemId,
577        /// Zero-based position within the parent array.
578        index: u32,
579    },
580}
581
582/// Shared state carried by every [`Element`] produced from the same parse.
583///
584/// Wrapped in [`Rc`] so that each element can resolve its own path without
585/// holding a live reference to the original [`Document`].
586#[derive(Debug)]
587struct DocumentInner<'buf> {
588    /// The full source string; all element spans are relative to this.
589    source: &'buf str,
590    /// Parent-pointer table used to reconstruct element paths.
591    paths: PathTable<'buf>,
592}
593
594/// A table recording the parentage of every [`Element`] produced by a parse.
595#[derive(Debug, Default)]
596struct PathTable<'buf> {
597    /// The `entries` `Vec` is indexed using `ElemId`.
598    entries: Vec<PathEntry<'buf>>,
599}
600
601impl<'buf> PathTable<'buf> {
602    fn push(&mut self, entry: PathEntry<'buf>) {
603        self.entries.push(entry);
604    }
605
606    /// Build the full RFC 9535 path to `element` by walking the parent-pointer chain.
607    ///
608    /// Cost is O(depth) per path construction.
609    ///
610    /// NOTE: The `'buf` lifetime shared by `element` and `self` prevents cross-document
611    /// misuse at compile time for documents with distinct source lifetimes.
612    ///
613    /// # Panics
614    ///
615    /// Panics if `element` was not produced by the same parse that created this table.
616    fn path_of(&self, element: &Element<'buf>) -> Path {
617        let mut entries: Vec<&PathEntry<'buf>> = Vec::new();
618        let mut elem_id = element.id;
619
620        // Walk back up the path chain.
621        loop {
622            let entry = self
623                .entries
624                .get(elem_id.0)
625                .expect("ElemId always refers to a valid PathEntry");
626
627            match entry {
628                PathEntry::Root => {
629                    entries.push(entry);
630                    break;
631                }
632                PathEntry::Field { parent, key: _ } | PathEntry::Item { parent, index: _ } => {
633                    entries.push(entry);
634                    elem_id = *parent;
635                }
636            }
637        }
638
639        // Reverse the elements so we can walk forward along the chain.
640        entries.reverse();
641
642        let mut out = String::with_capacity(30);
643
644        for entry in entries {
645            let res = match entry {
646                PathEntry::Root => write!(out, "$"),
647                PathEntry::Field { parent: _, key } => {
648                    write!(out, ".{}", key.as_unescaped_str())
649                }
650                PathEntry::Item { parent: _, index } => {
651                    // Array indices use bracket notation per RFC 9535, e.g.
652                    // `$.elements[0]`, rather than a dotted `.0` segment.
653                    write!(out, "[{index}]")
654                }
655            };
656
657            res.expect("Writing to a String can only fail if the system runs out of heap memory");
658        }
659
660        Path(out)
661    }
662}
663
664#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)]
665pub struct Path(String);
666
667impl Path {
668    pub fn into_string(self) -> String {
669        self.0
670    }
671
672    pub fn as_str(&self) -> &str {
673        &self.0
674    }
675
676    /// Iterate the [`Component`]s of this path in order, skipping the `$` root.
677    ///
678    /// For example `$.elements[0].id` yields `Member("elements")`,
679    /// `Index("0")`, `Member("id")`. The root path `$` yields nothing.
680    pub fn components(&self) -> Components<'_> {
681        Components::over(&self.0)
682    }
683}
684
685/// A single [`Path`] component: an object member or an array index.
686#[derive(Clone, Copy, Debug, PartialEq, Eq)]
687pub enum Component<'a> {
688    /// An object member, the `name` in a `.name` segment.
689    Member(&'a str),
690    /// An array index, the decimal digits in a `[n]` segment.
691    Index(&'a str),
692}
693
694/// Iterator over the [`Component`]s of a [`Path`]; see [`Path::components`].
695#[derive(Clone, Debug)]
696pub struct Components<'a> {
697    rest: &'a str,
698}
699
700impl<'a> Components<'a> {
701    /// Iterate the components of a raw `JSONPath` string, skipping a leading `$`.
702    pub(crate) fn over(path: &'a str) -> Self {
703        Self {
704            rest: path.strip_prefix('$').unwrap_or(path),
705        }
706    }
707}
708
709impl<'a> Iterator for Components<'a> {
710    type Item = Component<'a>;
711
712    fn next(&mut self) -> Option<Self::Item> {
713        if let Some(after) = self.rest.strip_prefix('.') {
714            // `.name`: read up to the next segment delimiter.
715            let end = after.find(['.', '[']).unwrap_or(after.len());
716            let (name, tail) = after.split_at(end);
717            self.rest = tail;
718            Some(Component::Member(name))
719        } else if let Some(after) = self.rest.strip_prefix('[') {
720            // `[index]`: read up to the closing bracket.
721            let end = after.find(']').unwrap_or(after.len());
722            let (index, tail) = after.split_at(end);
723            self.rest = tail.strip_prefix(']').unwrap_or(tail);
724            Some(Component::Index(index))
725        } else {
726            // Empty (root) or malformed input: stop iterating.
727            None
728        }
729    }
730}
731
732impl PartialEq<str> for Path {
733    fn eq(&self, other: &str) -> bool {
734        self.0 == other
735    }
736}
737
738impl PartialEq<&str> for Path {
739    fn eq(&self, other: &&str) -> bool {
740        self.0 == *other
741    }
742}
743
744impl fmt::Debug for Path {
745    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746        f.write_str(&self.0)
747    }
748}
749
750impl fmt::Display for Path {
751    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752        fmt::Display::fmt(&self.0, f)
753    }
754}
755
756/// Set of path with a common issue.
757#[derive(Debug)]
758pub struct PathSet<'set>(BTreeSet<&'set Path>);
759
760impl<'set> PathSet<'set> {
761    pub(crate) fn new(paths: BTreeSet<&'set Path>) -> Self {
762        Self(paths)
763    }
764
765    /// Return the field paths as a `Vec` of `String`s.
766    pub fn to_strings(&self) -> Vec<String> {
767        self.0.iter().map(ToString::to_string).collect()
768    }
769
770    /// Return the field paths as a `Vec` of `String`s.
771    pub fn into_strings(self) -> Vec<String> {
772        self.0.into_iter().map(ToString::to_string).collect()
773    }
774
775    /// Return true if the list of unexpected fields is empty.
776    pub fn is_empty(&self) -> bool {
777        self.0.is_empty()
778    }
779
780    /// Return the number of unexpected fields.
781    pub fn len(&self) -> usize {
782        self.0.len()
783    }
784
785    /// Return an Iterator over the unexpected fields.
786    pub fn iter(&self) -> btree_set::Iter<'_, &Path> {
787        self.0.iter()
788    }
789}
790
791impl<'set> IntoIterator for PathSet<'set> {
792    type Item = &'set Path;
793
794    type IntoIter = btree_set::IntoIter<&'set Path>;
795
796    fn into_iter(self) -> Self::IntoIter {
797        self.0.into_iter()
798    }
799}
800
801impl<'a, 'set> IntoIterator for &'a PathSet<'set> {
802    type Item = &'a &'set Path;
803
804    type IntoIter = btree_set::Iter<'a, &'set Path>;
805
806    fn into_iter(self) -> Self::IntoIter {
807        self.0.iter()
808    }
809}
810
811#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
812pub enum ValueKind {
813    Null,
814    Bool,
815    Number,
816    String,
817    Array,
818    Object,
819}
820
821impl fmt::Display for ValueKind {
822    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
823        match self {
824            ValueKind::Null => write!(f, "null"),
825            ValueKind::Bool => write!(f, "bool"),
826            ValueKind::Number => write!(f, "number"),
827            ValueKind::String => write!(f, "string"),
828            ValueKind::Array => write!(f, "array"),
829            ValueKind::Object => write!(f, "object"),
830        }
831    }
832}
833
834impl<'buf> Value<'buf> {
835    pub fn kind(&self) -> ValueKind {
836        match self {
837            Value::Null => ValueKind::Null,
838            Value::True | Value::False => ValueKind::Bool,
839            Value::String(_) => ValueKind::String,
840            Value::Number(_) => ValueKind::Number,
841            Value::Array(_) => ValueKind::Array,
842            Value::Object(_) => ValueKind::Object,
843        }
844    }
845
846    pub fn is_null(&self) -> bool {
847        matches!(self, Value::Null)
848    }
849
850    /// Return true if the `Value` is an array.
851    pub fn is_array(&self) -> bool {
852        matches!(self, Value::Array(..))
853    }
854
855    /// Return true if the `Value` is an object.
856    pub fn is_object(&self) -> bool {
857        matches!(self, Value::Object(..))
858    }
859
860    /// Return true if the `Value` can't contain child elements.
861    pub fn is_scalar(&self) -> bool {
862        matches!(
863            self,
864            Value::Null | Value::True | Value::False | Value::String(_) | Value::Number(_)
865        )
866    }
867
868    pub fn as_array(&self) -> Option<&[Element<'buf>]> {
869        if let Value::Array(elems) = self {
870            Some(elems)
871        } else {
872            None
873        }
874    }
875
876    pub fn as_number(&self) -> Option<&str> {
877        if let Value::Number(s) = self {
878            Some(s)
879        } else {
880            None
881        }
882    }
883
884    /// Return `Some(&str)` if the `Value` is a `String`.
885    pub fn to_raw_str(&self) -> Option<RawStr<'buf>> {
886        if let Value::String(s) = self {
887            Some(*s)
888        } else {
889            None
890        }
891    }
892
893    /// Return `Some(&[Field])` if the `Value` is a `Object`.
894    pub fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
895        if let Value::Object(fields) = self {
896            Some(fields)
897        } else {
898            None
899        }
900    }
901}
902
903/// An object field; upholds the invariant that the inner [`Element`]'s path ends with a key.
904#[derive(Clone, Debug, Eq, PartialEq)]
905pub struct Field<'buf> {
906    /// Span of the key token, including surrounding `"` delimiters.
907    key_span: Span,
908    /// The value element; its path ends with the key from `key_span`.
909    element: Element<'buf>,
910}
911
912impl<'buf> Field<'buf> {
913    /// Consume the `Field` and return the inner `Element`.
914    pub fn into_element(self) -> Element<'buf> {
915        self.element
916    }
917
918    /// Return the inner `Element`.
919    pub fn element(&self) -> &Element<'buf> {
920        &self.element
921    }
922
923    pub fn key_span(&self) -> Span {
924        self.key_span
925    }
926
927    /// Returns the span covering `"key": value` plus any trailing comma and whitespace.
928    ///
929    /// Choose the removal span based on the field's position in its parent:
930    ///
931    /// | Case | Span to erase |
932    /// |---|---|
933    /// | Remove non-last field | `field.full_span()` |
934    /// | Remove last field (siblings exist) | `fields[i-1].element().span().end .. field.element().span().end` |
935    /// | Remove only field | `field.element().span()` |
936    ///
937    /// When there is no trailing comma (last field), `full_span()` covers
938    /// `"key": value` only. Erasing it leaves a dangling comma on the previous
939    /// field; use the predecessor's `element().span().end` as the start instead.
940    pub fn full_span(&self) -> Span {
941        Span {
942            start: self.key_span.start,
943            end: self.element.full_span_end,
944        }
945    }
946
947    /// Returns the key text without surrounding `"` delimiters.
948    #[expect(
949        clippy::arithmetic_side_effects,
950        reason = "key_span always spans a quoted string, so +1/-1 to strip the surrounding quote bytes is safe"
951    )]
952    #[expect(
953        clippy::string_slice,
954        reason = "key_span is produced by the parser from the same source; +1/-1 strips the ASCII quote bytes"
955    )]
956    #[expect(
957        clippy::as_conversions,
958        reason = "The index is guaranteed within bounds by the parser"
959    )]
960    pub fn key(&self) -> RawStr<'buf> {
961        let src = self.element.source();
962        let s = &src[self.key_span.start as usize + 1..self.key_span.end as usize - 1];
963        RawStr::from_str(s)
964    }
965
966    /// Returns the slice of the source JSON spanning `"key": value`.
967    #[expect(
968        clippy::string_slice,
969        reason = "spans are produced by the parser from the same source, so slices are always valid"
970    )]
971    #[expect(
972        clippy::as_conversions,
973        reason = "The index is guaranteed within bounds by the parser"
974    )]
975    pub fn source_json(&self) -> &'buf str {
976        let src = self.element.source();
977        &src[self.key_span.start as usize..self.element.span.end as usize]
978    }
979}
980
981pub type RawMap<'buf> = BTreeMap<RawStr<'buf>, Element<'buf>>;
982pub type RawRefMap<'a, 'buf> = BTreeMap<RawStr<'buf>, &'a Element<'buf>>;
983
984#[expect(dead_code, reason = "pending use in `tariff::lint`")]
985pub(crate) trait FieldsIntoExt<'buf> {
986    fn into_map(self) -> RawMap<'buf>;
987}
988
989pub(crate) trait FieldsAsExt<'buf> {
990    fn as_raw_map(&self) -> RawRefMap<'_, 'buf>;
991    fn find_field(&self, key: &str) -> Option<&Field<'buf>>;
992}
993
994impl<'buf> FieldsIntoExt<'buf> for Vec<Field<'buf>> {
995    fn into_map(self) -> RawMap<'buf> {
996        self.into_iter()
997            .map(|field| (field.key(), field.into_element()))
998            .collect()
999    }
1000}
1001
1002impl<'buf> FieldsAsExt<'buf> for Vec<Field<'buf>> {
1003    fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
1004        self.iter()
1005            .map(|field| (field.key(), field.element()))
1006            .collect()
1007    }
1008
1009    fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
1010        self.iter()
1011            .find(|field| field.key().eq_escape_aware(key).ok().unwrap_or(false))
1012    }
1013}
1014
1015impl<'buf> FieldsAsExt<'buf> for [Field<'buf>] {
1016    fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
1017        self.iter()
1018            .map(|field| (field.key(), field.element()))
1019            .collect()
1020    }
1021
1022    fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
1023        self.iter()
1024            .find(|field| field.key().eq_escape_aware(key).ok().unwrap_or(false))
1025    }
1026}
1027
1028/// A `&str` with surrounding quotes removed; escape sequences are not decoded.
1029#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
1030pub struct RawStr<'buf>(&'buf str);
1031
1032/// Impl `Borrow` so `RawStr` plays well with hashed collections.
1033impl Borrow<str> for RawStr<'_> {
1034    fn borrow(&self) -> &str {
1035        self.0
1036    }
1037}
1038
1039/// Impl `Borrow` so `RawStr` plays well with hashed collections.
1040impl Borrow<str> for &RawStr<'_> {
1041    fn borrow(&self) -> &str {
1042        self.0
1043    }
1044}
1045
1046impl<'buf> RawStr<'buf> {
1047    fn from_str(source: &'buf str) -> Self {
1048        Self(source)
1049    }
1050
1051    /// Compare `other` against this raw `&str`, decoding any JSON escape
1052    /// sequences in the `&str` on the fly without allocating.
1053    ///
1054    /// Returns `Ok(true)`/`Ok(false)` for the comparison, or `Err` if the key
1055    /// contains a decoding problem (an invalid escape or a control character) at
1056    /// or before the first differing character.
1057    pub fn eq_escape_aware(&self, other: &str) -> Result<bool, decode::Warning> {
1058        decode::eq(self.0, other)
1059    }
1060
1061    /// Compare this raw `&str` against a list of `&str`s, decoding any JSON escape
1062    /// sequences in the `&str` on the fly without allocating.
1063    ///
1064    /// Returns true if any of the `other` `&str`s match self.
1065    pub fn eq_any_escape_aware(&self, other: &[&str]) -> bool {
1066        other
1067            .iter()
1068            .any(|s| decode::eq(self.0, s).ok().unwrap_or(false))
1069    }
1070
1071    /// Like [`RawStr::eq_any_escape_aware`], but compares ASCII letters case-insensitively.
1072    pub fn eq_any_escape_aware_ignore_ascii_case(&self, other: &[&str]) -> bool {
1073        other.iter().any(|s| {
1074            decode::eq_ignore_ascii_case(self.0, s)
1075                .ok()
1076                .unwrap_or(false)
1077        })
1078    }
1079
1080    /// Return the raw unescaped `&str`.
1081    pub fn as_unescaped_str(&self) -> &'buf str {
1082        self.0
1083    }
1084
1085    /// Return the `&str` with all escapes decoded.
1086    pub fn decode_escapes(&self) -> CaveatDeferred<Cow<'_, str>, decode::Warning> {
1087        decode::from_raw(self.0)
1088    }
1089
1090    /// Return a `&str` marked as either having escapes or not.
1091    pub fn has_escapes(&self, elem: &Element<'buf>) -> Caveat<PendingStr<'buf>, decode::Warning> {
1092        decode::analyze(self.0, elem)
1093    }
1094}
1095
1096/// Marks a `&str` as having escapes or not.
1097pub enum PendingStr<'buf> {
1098    /// The `&str` has no escapes and can be used as is.
1099    NoEscapes(&'buf str),
1100
1101    /// The `&str` has escape chars and needs to be unescaped before trying to parse into another form.
1102    HasEscapes(EscapeStr<'buf>),
1103}
1104
1105/// A `&str` with escape chars.
1106pub struct EscapeStr<'buf>(&'buf str);
1107
1108impl<'buf> EscapeStr<'buf> {
1109    pub fn decode_escapes(&self) -> CaveatDeferred<Cow<'buf, str>, decode::Warning> {
1110        decode::from_raw(self.0)
1111    }
1112
1113    /// Consume the `EscapeStr` and return the raw bytes as a str.
1114    pub fn into_raw(self) -> &'buf str {
1115        self.0
1116    }
1117}
1118
1119/// A type that can be created from a parsed [`Element`].
1120///
1121/// Implementors return a [`Verdict`]: either the converted value with zero or
1122/// more nonfatal warnings, or a fatal [`warning::ErrorSet`] that aborted creation.
1123pub(crate) trait FromJson<'buf>: Sized {
1124    /// Warning type emitted when creation issues are detected.
1125    type Warning: warning::Warning;
1126
1127    /// Convert `elem` to `Self`, collecting any nonfatal issues as warnings.
1128    fn from_json(elem: &Element<'buf>) -> Verdict<Self, Self::Warning>;
1129}
1130
1131/// Implement a `null` aware variant of all types that implement `FromJson`.
1132///
1133/// If the value of the `json::Element` is `null` then return a `None`.
1134impl<'buf, T> FromJson<'buf> for Option<T>
1135where
1136    T: FromJson<'buf> + IntoCaveat,
1137{
1138    type Warning = T::Warning;
1139
1140    fn from_json(elem: &Element<'buf>) -> Verdict<Self, Self::Warning> {
1141        let value = elem.as_value();
1142
1143        if value.is_null() {
1144            Ok(None.into_caveat(Set::new()))
1145        } else {
1146            let v = T::from_json(elem)?;
1147            Ok(v.map(Some))
1148        }
1149    }
1150}