sophia_api/source/
_quad.rs

1use std::error::Error;
2
3use super::*;
4use crate::dataset::{CollectibleDataset, Dataset, MutableDataset};
5use crate::quad::Quad;
6
7/// A quad source is a [`Source`] producing [quads](Quad).
8///
9/// This trait extends the [`Source`] trait with quad-specific methods.
10///
11/// It does not need to be explicitly implemented:
12/// any [`Source`] implementation producing [quads](Quad)
13/// will automatically implement [`QuadSource`].
14///
15/// See also [`QSQuad`].
16pub trait QuadSource: Source + IsQuadSource {
17    /// Call f for some quad(s) (possibly zero) from this source, if any.
18    ///
19    /// Return `Ok(false)` if there are no more quads in this source.
20    ///
21    /// Return an error if either the source or `f` errs.
22    #[inline]
23    fn try_for_some_quad<E, F>(&mut self, mut f: F) -> StreamResult<bool, Self::Error, E>
24    where
25        E: Error + Send + Sync + 'static,
26        F: FnMut(Self::Quad<'_>) -> Result<(), E>,
27    {
28        self.try_for_some_item(|i| f(Self::i2q(i)))
29    }
30
31    /// Call f for all quads from this source.
32    ///
33    /// Return an error if either the source or `f` errs.
34    #[inline]
35    fn try_for_each_quad<F, E>(&mut self, mut f: F) -> StreamResult<(), Self::Error, E>
36    where
37        F: FnMut(Self::Quad<'_>) -> Result<(), E>,
38        E: Error + Send + Sync + 'static,
39    {
40        self.try_for_each_item(|i| f(Self::i2q(i)))
41    }
42
43    /// Call f for some quad(s) (possibly zero) from this source, if any.
44    ///
45    /// Return false if there are no more quads in this source.
46    ///
47    /// Return an error if either the source errs.
48    #[inline]
49    fn for_some_quad<F>(&mut self, mut f: F) -> Result<bool, Self::Error>
50    where
51        F: FnMut(Self::Quad<'_>),
52    {
53        self.for_some_item(|i| f(Self::i2q(i)))
54    }
55
56    /// Call f for all quads from this source.
57    ///
58    /// Return an error if either the source errs.
59    #[inline]
60    fn for_each_quad<F>(&mut self, mut f: F) -> Result<(), Self::Error>
61    where
62        F: FnMut(Self::Quad<'_>),
63    {
64        self.for_each_item(|i| f(Self::i2q(i)))
65    }
66
67    /// Returns a source which uses `predicate` to determine if an quad should be yielded.
68    #[inline]
69    fn filter_quads<'f, F>(
70        self,
71        mut predicate: F,
72    ) -> filter::FilterQuadSource<Self, impl FnMut(&Self::Item<'_>) -> bool + 'f>
73    where
74        Self: Sized,
75        F: FnMut(&QSQuad<Self>) -> bool + 'f,
76    {
77        filter::FilterQuadSource(self.filter_items(move |i| predicate(Self::ri2q(i))))
78    }
79
80    /// Returns a source that both filters and maps.
81    ///
82    /// See also [`QuadSource::filter_quads`] and [`QuadSource::map_quads`].
83    #[inline]
84    fn filter_map_quads<'f, F, T>(
85        self,
86        mut filter_map: F,
87    ) -> filter_map::FilterMapSource<Self, impl FnMut(Self::Item<'_>) -> Option<T> + 'f>
88    where
89        Self: Sized,
90        F: FnMut(Self::Quad<'_>) -> Option<T> + 'f,
91    {
92        self.filter_map_items(move |i| filter_map(Self::i2q(i)))
93    }
94
95    /// Returns a source which yield the result of `map` for each quad.
96    ///
97    /// See also [`QuadSource::to_triples`].
98    ///
99    /// NB: due to [some limitations in GATsĀ (Generic) Associated Types](https://blog.rust-lang.org/2022/10/28/gats-stabilization.html),
100    /// the `map` function is currently restricted in what it can return.
101    /// In particular, passing functions as trivial as `|q| q` or `|q| q.to_spog()`
102    /// currently do not compile on all implementations of [`QuadSource`].
103    /// Furthermore, some functions returning a [`Quad`] are accepted,
104    /// but fail to make the resulting [`map::MapSource`] recognized as a [`QuadSource`].
105    ///
106    /// As a rule of thumb,
107    /// whenever `map` returns something satisfying the `'static` lifetime,
108    /// things should work as expected.
109    #[inline]
110    fn map_quads<'f, F, T>(
111        self,
112        mut map: F,
113    ) -> map::MapSource<Self, impl FnMut(Self::Item<'_>) -> T + 'f>
114    where
115        Self: Sized,
116        F: FnMut(Self::Quad<'_>) -> T + 'f,
117    {
118        self.map_items(move |i| map(Self::i2q(i)))
119    }
120
121    /// Convert of quads in this source to triples (stripping the graph name).
122    fn to_triples(self) -> convert::ToTriples<Self>
123    where
124        Self: Sized,
125    {
126        convert::ToTriples(self)
127    }
128
129    /// Returns the bounds on the remaining length of the source.
130    ///
131    /// This method has the same contract as [`Iterator::size_hint`].
132    fn size_hint_quads(&self) -> (usize, Option<usize>) {
133        (0, None)
134    }
135
136    /// Collect these quads into a new dataset.
137    #[inline]
138    fn collect_quads<D>(self) -> StreamResult<D, Self::Error, <D as Dataset>::Error>
139    where
140        Self: Sized,
141        for<'x> Self::Quad<'x>: Quad,
142        D: CollectibleDataset,
143    {
144        D::from_quad_source(self)
145    }
146
147    /// Insert all quads from this source into the given [MutableDataset].
148    ///
149    /// Stop on the first error (in the source or in the dataset).
150    #[inline]
151    fn add_to_dataset<D: MutableDataset>(
152        self,
153        dataset: &mut D,
154    ) -> StreamResult<usize, Self::Error, <D as MutableDataset>::MutationError>
155    where
156        Self: Sized,
157        for<'x> Self::Quad<'x>: Quad,
158    {
159        dataset.insert_all(self)
160    }
161}
162
163/// Ensures that QuadSource acts as an type alias for any Source satisfying the conditions.
164impl<T> QuadSource for T where T: Source + IsQuadSource {}
165
166/// Type alias to denote the type of quads yielded by a [`QuadSource`].
167///
168/// **Why not using `TS::Item<'a>` instead?**
169/// [`Source::Item`] being a generic associated type (GAT),
170/// the compiler will not always "know" that `TS::Item<'a>` implements the [`Quad`] trait.
171/// This type alias, on the other hand, will always be recognized as a [`Quad`] implementation.
172pub type QSQuad<'a, TS> = <TS as IsQuadSource>::Quad<'a>;
173
174mod sealed {
175    use super::*;
176
177    pub trait IsQuadSource: Source {
178        type Quad<'x>: Quad;
179        fn i2q(i: Self::Item<'_>) -> Self::Quad<'_>;
180        fn ri2q<'a, 'b>(i: &'a Self::Item<'b>) -> &'a Self::Quad<'b>;
181    }
182
183    impl<TS> IsQuadSource for TS
184    where
185        TS: Source,
186        for<'x> TS::Item<'x>: Quad,
187    {
188        type Quad<'x> = Self::Item<'x>;
189
190        fn i2q(i: Self::Item<'_>) -> Self::Quad<'_> {
191            i
192        }
193
194        fn ri2q<'a, 'b>(i: &'a Self::Item<'b>) -> &'a Self::Quad<'b> {
195            i
196        }
197    }
198}
199use sealed::IsQuadSource;
200
201#[cfg(test)]
202mod check_quad_source {
203    use super::*;
204    use crate::quad::Spog;
205    use crate::term::{SimpleTerm, Term};
206    use sophia_iri::IriRef;
207    use std::convert::Infallible;
208    use std::fmt::Write;
209
210    #[allow(dead_code)] // only checks that this compiles
211    pub fn check_for_each<TS>(ts: TS)
212    where
213        TS: QuadSource,
214    {
215        ts.filter_quads(|t| t.s().is_iri())
216            .for_each_quad(|t| println!("{:?}", t.s()))
217            .unwrap();
218    }
219
220    #[allow(dead_code)] // only checks that this compiles
221    fn check_quad_source_impl_generic_dataset<D: Dataset>(d: &D) {
222        d.quads()
223            .filter_quads(|t| t.s().is_iri())
224            .for_each_quad(|t| println!("{:?}", t.s()))
225            .unwrap();
226    }
227
228    #[allow(dead_code)] // only checks that this compiles
229    fn check_quad_source_impl_concrete_dataset(d: &[Spog<SimpleTerm>]) {
230        d.quads()
231            .filter_quads(|t| t.s().is_iri())
232            .for_each_quad(|t| println!("{:?}", t.s()))
233            .unwrap();
234    }
235
236    // checking that QuadSource can be implemented
237    struct DummyParser<'a> {
238        tokens: &'a [usize],
239        pos: usize,
240        buffers: [String; 3],
241    }
242
243    impl<'a> Source for DummyParser<'a> {
244        type Item<'x> = Spog<SimpleTerm<'x>>;
245        type Error = Infallible;
246
247        fn try_for_some_item<E2, F>(&mut self, mut f: F) -> StreamResult<bool, Self::Error, E2>
248        where
249            E2: Error,
250            F: FnMut(Self::Item<'_>) -> Result<(), E2>,
251        {
252            if self.tokens.len() - self.pos < 3 {
253                Ok(false)
254            } else {
255                for i in 0..3 {
256                    write!(&mut self.buffers[i], "b{}", self.tokens[self.pos + i]).unwrap();
257                }
258                let q = (
259                    [
260                        IriRef::new_unchecked(&self.buffers[0][..]).into_term(),
261                        IriRef::new_unchecked(&self.buffers[1][..]).into_term(),
262                        IriRef::new_unchecked(&self.buffers[2][..]).into_term(),
263                    ],
264                    None,
265                );
266                f(q).map_err(SinkError).map(|_| true)
267            }
268        }
269    }
270
271    #[allow(dead_code)] // only checks that this compiles
272    fn check_quad_source_impl_by_iterator(v: Vec<Result<Spog<SimpleTerm>, std::io::Error>>) {
273        v.into_iter()
274            .for_each_quad(|q| println!("{:?}", q.s()))
275            .unwrap();
276    }
277
278    /* Currently does not work...
279    #[allow(dead_code)]
280    fn check_quad_source_map<S: QuadSource>(mut qs: S) {
281        qs.for_each_quad(|q| println!("{:?}", q.s())).unwrap();
282        qs.map_quads(|q| q)
283            .for_each_quad(|q| println!("{:?}", q.s()))
284            .unwrap();
285    }
286    */
287}