Skip to main content

rsonpath/
result.rs

1//! Result types that can be returned by a JSONPath query engine.
2use crate::{depth::Depth, engine::error::EngineError};
3use std::{convert::Infallible, fmt::Display, io, ops::Deref};
4
5pub mod approx_span;
6pub mod count;
7pub mod empty;
8pub mod index;
9pub mod nodes;
10mod output_queue;
11
12/// Result of counting query matches.
13pub type MatchCount = u64;
14
15/// Representation of the starting index of a match.
16pub type MatchIndex = usize;
17
18/// Span of a match – its start and end index.
19///
20/// The end index is **exclusive**. For example, the value
21/// `true` may have the span of `(17, 21)`, meaning that
22/// the first character, 't', occurs at index 17, and the last
23/// character, `e` occurs at index 20.
24///
25/// This is in line with what a `[17..21]` slice in Rust represents.
26#[derive(Clone, Copy)]
27pub struct MatchSpan {
28    /// Starting index of the match.
29    start_idx: MatchIndex,
30    /// Length of the match
31    len: usize,
32}
33
34/// Full information of a query match – its span and the input bytes
35/// in that span.
36pub struct Match {
37    /// JSON contents of the match.
38    bytes: Vec<u8>,
39    /// Starting index of the match.
40    span_start: usize,
41}
42
43impl MatchSpan {
44    pub(crate) fn from_indices(start_idx: usize, end_idx: usize) -> Self {
45        assert!(
46            start_idx <= end_idx,
47            "start of span {start_idx} is greater than end {end_idx}"
48        );
49        Self {
50            start_idx,
51            len: end_idx - start_idx,
52        }
53    }
54
55    /// Returns the starting index of the match.
56    #[inline(always)]
57    #[must_use]
58    pub fn start_idx(&self) -> usize {
59        self.start_idx
60    }
61
62    /// Returns the end index of the match.
63    #[inline(always)]
64    #[must_use]
65    pub fn end_idx(&self) -> usize {
66        self.start_idx + self.len
67    }
68
69    /// Returns the length of the match.
70    #[inline(always)]
71    #[must_use]
72    #[allow(
73        clippy::len_without_is_empty,
74        reason = "is_empty makes no sense for a match (matches are non-empty)"
75    )]
76    pub fn len(&self) -> usize {
77        self.len
78    }
79}
80
81impl Match {
82    pub(crate) fn from_start_and_bytes(span_start: usize, bytes: Vec<u8>) -> Self {
83        Self { bytes, span_start }
84    }
85
86    /// Returns the JSON contents of the match.
87    #[inline(always)]
88    #[must_use]
89    pub fn bytes(&self) -> &[u8] {
90        &self.bytes
91    }
92
93    /// Consumes the [`Match`] to take ownership of the underlying JSON bytes.
94    #[inline(always)]
95    #[must_use]
96    pub fn into_bytes(self) -> Vec<u8> {
97        self.bytes
98    }
99
100    /// Returns the span of this match in the JSON document:
101    /// its starting and ending byte indices.
102    #[inline(always)]
103    #[must_use]
104    pub fn span(&self) -> MatchSpan {
105        MatchSpan {
106            start_idx: self.span_start,
107            len: self.bytes.len(),
108        }
109    }
110}
111
112impl Display for MatchSpan {
113    #[inline]
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        write!(f, "[{}..{}]", self.start_idx, self.end_idx())
116    }
117}
118
119impl Display for Match {
120    #[inline]
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        let display = String::from_utf8_lossy(&self.bytes);
123        write!(f, "{display}")
124    }
125}
126
127/// Output sink consuming matches of the type `D`.
128pub trait Sink<D> {
129    /// Error type that can be raised when consuming a match.
130    type Error: std::error::Error + Send + Sync + 'static;
131
132    /// Consume a single match of type `D`.
133    ///
134    /// # Errors
135    /// An error depending on the implementor can be raised.
136    /// For example, implementations using an underlying [`io::Write`]
137    /// may raise an [`io::Error`].
138    fn add_match(&mut self, data: D) -> Result<(), Self::Error>;
139}
140
141impl<D> Sink<D> for Vec<D> {
142    type Error = Infallible;
143
144    #[inline(always)]
145    fn add_match(&mut self, data: D) -> Result<(), Infallible> {
146        self.push(data);
147        Ok(())
148    }
149}
150
151/// Empty sink that consumes all matches into the void.
152pub struct NullSink;
153
154impl<D> Sink<D> for NullSink {
155    type Error = Infallible;
156
157    #[inline(always)]
158    fn add_match(&mut self, _data: D) -> Result<(), Infallible> {
159        Ok(())
160    }
161}
162
163/// Thin wrapper over an [`io::Write`] to provide a [`Sink`] impl.
164pub struct MatchWriter<W>(W);
165
166impl<W> From<W> for MatchWriter<W>
167where
168    W: io::Write,
169{
170    #[inline(always)]
171    fn from(value: W) -> Self {
172        Self(value)
173    }
174}
175
176impl<D, W> Sink<D> for MatchWriter<W>
177where
178    D: Display,
179    W: io::Write,
180{
181    type Error = io::Error;
182
183    #[inline(always)]
184    fn add_match(&mut self, data: D) -> Result<(), io::Error> {
185        writeln!(self.0, "{data}")
186    }
187}
188
189/// Type of a value being reported to a [`Recorder`].
190#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
191pub enum MatchedNodeType {
192    /// JSON string, number, or literal value.
193    Atomic,
194    /// JSON object or array.
195    Complex,
196}
197
198/// Base trait of any recorder, one that can react to a block of input being processed.
199pub trait InputRecorder<B: Deref<Target = [u8]>> {
200    /// Record that all processing of a block was started
201    ///
202    /// The recorder may assume that only matches or terminators with indices pointing to
203    /// the block that was last recorded as started are reported.
204    fn record_block_start(&self, new_block: B);
205}
206
207/// An observer that can determine the query result
208/// based on match and structural events coming from the execution engine.
209pub trait Recorder<B: Deref<Target = [u8]>>: InputRecorder<B> {
210    /// Record a match of the query at a given `depth`.
211    /// The `idx` is guaranteed to be the first character of the matched value.
212    ///
213    /// The type MUST accurately describe the value being matched.
214    ///
215    /// # Errors
216    /// An error can be raised if an output write occurs and the underlying [`Sink`] implementation
217    /// returns an error ([`EngineError::SinkError`]).
218    fn record_match(&self, idx: usize, depth: Depth, ty: MatchedNodeType) -> Result<(), EngineError>;
219
220    /// Record a structural character signifying the end of a value at a given `idx`
221    /// and with given `depth`.
222    ///
223    /// # Errors
224    /// An error can be raised if an output write occurs and the underlying [`Sink`] implementation
225    /// returns an error ([`EngineError::SinkError`]), or if the terminator was not expected
226    /// ([`EngineError::MissingOpeningCharacter`]).
227    fn record_value_terminator(&self, idx: usize, depth: Depth) -> Result<(), EngineError>;
228}