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