Skip to main content

pict_rs_typed_path/
lib.rs

1mod flee;
2
3use std::{
4    borrow::Cow,
5    fmt::{Debug, Display},
6    marker::PhantomData,
7    ops::Deref,
8};
9
10use exn::{Exn, ResultExt};
11
12#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct PathGen;
14
15#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct Prefix<Inner, Segment> {
17    inner: Inner,
18    segment: PhantomData<Segment>,
19}
20
21#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct Node<Inner, Segment> {
23    inner: Inner,
24    segment: Segment,
25}
26
27impl<'a, Inner, Segment> Debug for Prefix<Inner, Segment>
28where
29    Inner: Sealed + Debug,
30    Segment: PathNode<'a>,
31{
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(
34            f,
35            "{:?}.{:?}",
36            self.inner,
37            String::from_utf8_lossy(Segment::NAME)
38        )
39    }
40}
41
42impl<'a, Segment> Debug for Prefix<PathGen, Segment>
43where
44    Segment: PathNode<'a>,
45{
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        write!(f, "{:?}", String::from_utf8_lossy(Segment::NAME))
48    }
49}
50
51impl<'a, Inner, Segment> Debug for Node<Inner, Segment>
52where
53    Inner: Sealed + Debug,
54    Segment: PathNode<'a>,
55    Segment: Debug,
56{
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(
59            f,
60            "{:?}.{:?}.{:?}",
61            self.inner,
62            String::from_utf8_lossy(Segment::NAME),
63            self.segment
64        )
65    }
66}
67
68impl<'a, Segment> Debug for Node<PathGen, Segment>
69where
70    Segment: PathNode<'a>,
71    Segment: Debug,
72{
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        write!(
75            f,
76            "{:?}.{:?}",
77            String::from_utf8_lossy(Segment::NAME),
78            self.segment
79        )
80    }
81}
82
83#[derive(Debug)]
84pub enum DeconstructError {
85    Format,
86    PathItem { kind: &'static str },
87}
88
89impl Display for DeconstructError {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match self {
92            Self::Format => f.write_str("Invalid path format"),
93            Self::PathItem { kind } => write!(f, "Invalid bytes for PathItem {kind}"),
94        }
95    }
96}
97
98impl std::error::Error for DeconstructError {}
99
100pub struct PathParser<Inner> {
101    phantom: PhantomData<Inner>,
102}
103
104impl<Inner> std::fmt::Debug for PathParser<Inner> {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        f.debug_struct("PathParser")
107            .field("phantom", &self.phantom)
108            .finish()
109    }
110}
111
112impl<Inner> std::fmt::Display for PathParser<Inner> {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        write!(f, "PathParser<{}>", std::any::type_name::<Inner>())
115    }
116}
117
118pub trait PathItem<'a> {
119    type Error: std::error::Error + Send + Sync + 'static;
120
121    fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
122    where
123        Self: Sized + 'a;
124
125    fn to_bytes(&self) -> Cow<'_, [u8]>;
126}
127
128pub trait PathNode<'a>: PathItem<'a> {
129    const NAME: &'static [u8];
130}
131
132impl PathGen {
133    pub const fn parser() -> PathParser<Self> {
134        PathParser {
135            phantom: PhantomData,
136        }
137    }
138
139    pub const fn push<'a, N>(self, node: N) -> Node<Self, N>
140    where
141        N: PathNode<'a>,
142    {
143        Node {
144            inner: self,
145            segment: node,
146        }
147    }
148
149    pub const fn prefix<'a, N>(self) -> Prefix<Self, N>
150    where
151        N: PathNode<'a>,
152    {
153        Prefix {
154            inner: self,
155            segment: PhantomData,
156        }
157    }
158}
159
160impl<Inner, Segment> Node<Inner, Segment> {
161    pub const fn push<'a, N>(self, node: N) -> Node<Self, N>
162    where
163        N: PathNode<'a>,
164    {
165        Node {
166            inner: self,
167            segment: node,
168        }
169    }
170
171    pub fn split(self) -> (Inner, Segment) {
172        (self.inner, self.segment)
173    }
174
175    pub const fn segment(&self) -> &Segment {
176        &self.segment
177    }
178
179    pub const fn prefix<'a, N>(self) -> Prefix<Self, N>
180    where
181        N: PathNode<'a>,
182    {
183        Prefix {
184            inner: self,
185            segment: PhantomData,
186        }
187    }
188}
189
190impl<Inner, Segment> Prefix<Inner, Segment> {
191    pub fn pop(self) -> Inner {
192        self.inner
193    }
194}
195
196impl<Inner, Segment> Prefix<Inner, Segment>
197where
198    Self: Construct,
199{
200    pub fn to_bytes(&self) -> Vec<u8> {
201        Construct::construct(self)
202    }
203}
204
205impl<Inner> PathParser<Inner> {
206    pub const fn push<'a, N>(self) -> PathParser<Node<Inner, N>>
207    where
208        N: PathNode<'a>,
209    {
210        PathParser {
211            phantom: PhantomData,
212        }
213    }
214
215    pub const fn prefix<'a, N>(self) -> PathParser<Prefix<Inner, N>>
216    where
217        N: PathNode<'a>,
218    {
219        PathParser {
220            phantom: PhantomData,
221        }
222    }
223}
224
225impl<Inner> PathParser<Inner> {
226    pub fn parse<'a>(&self, s: &'a [u8]) -> Result<Inner, Exn<DeconstructError>>
227    where
228        Inner: Deconstruct<'a> + 'a,
229    {
230        Inner::deconstruct(s)
231    }
232}
233
234impl<Inner, Segment> Deref for Node<Inner, Segment> {
235    type Target = Segment;
236
237    fn deref(&self) -> &Self::Target {
238        self.segment()
239    }
240}
241
242mod hidden {
243    use super::DeconstructError;
244
245    use exn::Exn;
246
247    pub trait Construct {
248        fn construct(&self) -> Vec<u8>;
249    }
250
251    pub trait Sealed {}
252
253    pub trait Deconstruct<'a> {
254        fn deconstruct(s: &'a [u8]) -> Result<Self, Exn<DeconstructError>>
255        where
256            Self: Sized + 'a;
257    }
258}
259use hidden::{Construct, Deconstruct, Sealed};
260
261impl<'a, Inner, N> Sealed for Node<Inner, N>
262where
263    Inner: Sealed,
264    N: PathNode<'a>,
265{
266}
267
268impl<'a, N> Sealed for Node<PathGen, N> where N: PathNode<'a> {}
269
270impl<'a, Inner, N> Deconstruct<'a> for Node<Inner, N>
271where
272    Inner: Deconstruct<'a> + Sealed,
273    N: PathNode<'a>,
274{
275    fn deconstruct(s: &'a [u8]) -> Result<Self, Exn<DeconstructError>>
276    where
277        Self: Sized + 'a,
278    {
279        if let Some((rest, value)) = rsplit_once_escaped(s)
280            && let Some((rest, name)) = rsplit_once_escaped(rest)
281            && unescape(name) == N::NAME
282        {
283            let inner = Inner::deconstruct(rest)?;
284            let unescaped = unescape(value);
285            let segment = N::parse(unescaped).or_raise(|| DeconstructError::PathItem {
286                kind: std::any::type_name::<N>(),
287            })?;
288            return Ok(Node { inner, segment });
289        }
290
291        Err(Exn::new(DeconstructError::Format))
292    }
293}
294
295impl<'a, N> Deconstruct<'a> for Node<PathGen, N>
296where
297    N: PathNode<'a>,
298{
299    fn deconstruct(s: &'a [u8]) -> Result<Self, Exn<DeconstructError>>
300    where
301        Self: Sized + 'a,
302    {
303        if let Some((name, value)) = rsplit_once_escaped(s)
304            && unescape(name) == N::NAME
305        {
306            let unescaped = unescape(value);
307            let segment = N::parse(unescaped).or_raise(|| DeconstructError::PathItem {
308                kind: std::any::type_name::<N>(),
309            })?;
310            return Ok(Node {
311                inner: PathGen,
312                segment,
313            });
314        }
315
316        Err(Exn::new(DeconstructError::Format))
317    }
318}
319
320impl<'a, Inner, N> Sealed for Prefix<Inner, N>
321where
322    Inner: Sealed,
323    N: PathNode<'a>,
324{
325}
326impl<'a, N> Sealed for Prefix<PathGen, N> where N: PathNode<'a> {}
327
328impl<'a, Inner, N> Deconstruct<'a> for Prefix<Inner, N>
329where
330    Inner: Deconstruct<'a> + Sealed,
331    N: PathNode<'a>,
332{
333    fn deconstruct(s: &'a [u8]) -> Result<Self, Exn<DeconstructError>>
334    where
335        Self: Sized + 'a,
336    {
337        if let Some((rest, name)) = rsplit_once_escaped(s)
338            && unescape(name) == N::NAME
339        {
340            let inner = Inner::deconstruct(rest)?;
341            return Ok(Prefix {
342                inner,
343                segment: PhantomData,
344            });
345        }
346
347        Err(Exn::new(DeconstructError::Format))
348    }
349}
350
351impl<'a, N> Deconstruct<'a> for Prefix<PathGen, N>
352where
353    N: PathNode<'a>,
354{
355    fn deconstruct(s: &'a [u8]) -> Result<Self, Exn<DeconstructError>>
356    where
357        Self: Sized + 'a,
358    {
359        if unescape(s) == N::NAME {
360            return Ok(Prefix {
361                inner: PathGen,
362                segment: PhantomData,
363            });
364        }
365
366        Err(Exn::new(DeconstructError::Format))
367    }
368}
369
370impl<'a, Inner, N> Construct for Node<Inner, N>
371where
372    Inner: Construct,
373    N: PathNode<'a>,
374{
375    fn construct(&self) -> Vec<u8> {
376        let mut vec = self.inner.construct();
377        vec.extend_from_slice(b".");
378        vec.extend_from_slice(&escape(N::NAME));
379        vec.extend_from_slice(b".");
380        vec.extend_from_slice(&escape(&self.segment.to_bytes()));
381        vec
382    }
383}
384
385impl<'a, N> Construct for Node<PathGen, N>
386where
387    N: PathNode<'a>,
388{
389    fn construct(&self) -> Vec<u8> {
390        let mut vec = escape(N::NAME).into_owned();
391        vec.extend_from_slice(b".");
392        vec.extend_from_slice(&escape(&self.segment.to_bytes()));
393        vec
394    }
395}
396
397impl<'a, Inner, N> Construct for Prefix<Inner, N>
398where
399    Inner: Construct,
400    N: PathNode<'a>,
401{
402    fn construct(&self) -> Vec<u8> {
403        let mut vec = self.inner.construct();
404        vec.extend_from_slice(b".");
405        vec.extend_from_slice(&escape(N::NAME));
406        vec
407    }
408}
409
410impl<'a, N> Construct for Prefix<PathGen, N>
411where
412    N: PathNode<'a>,
413{
414    fn construct(&self) -> Vec<u8> {
415        escape(N::NAME).into_owned()
416    }
417}
418
419const DELIMITER: u8 = b'.';
420const ESCAPE_TOKEN: u8 = b'\\';
421
422fn escape(s: &[u8]) -> Cow<'_, [u8]> {
423    flee::escape(s, DELIMITER, ESCAPE_TOKEN)
424}
425
426fn unescape(s: &[u8]) -> Cow<'_, [u8]> {
427    flee::unescape(s, DELIMITER, ESCAPE_TOKEN)
428}
429
430fn rsplit_once_escaped(s: &[u8]) -> Option<(&[u8], &[u8])> {
431    flee::rsplit_once_escaped(s, DELIMITER, ESCAPE_TOKEN)
432}
433
434#[cfg(test)]
435mod tests {
436    use std::{
437        borrow::Cow,
438        fmt::Debug,
439        num::ParseIntError,
440        str::{Utf8Error, from_utf8},
441    };
442
443    fn test_pathnode<'a, 'b, N, N2, E>(path_node: N)
444    where
445        N: PathNode<'a, Error = E> + Debug,
446        N2: PathNode<'b, Error = E> + Debug + PartialEq<N> + 'b,
447        E: Debug,
448    {
449        test_roundtrip::<'a, 'b, N, N2, E>(path_node);
450    }
451
452    fn test_roundtrip<'a, 'b, T, T2, E>(item: T)
453    where
454        T: PathItem<'a, Error = E> + Debug,
455        T2: PathItem<'b, Error = E> + Debug + PartialEq<T> + 'b,
456        E: Debug,
457    {
458        let vec = item.to_bytes().to_vec();
459        let round_trip = T2::parse(Cow::Owned(vec.clone())).unwrap();
460        let plus_one = round_trip.to_bytes().to_vec();
461
462        assert_eq!(round_trip, item);
463        assert_eq!(vec, plus_one);
464    }
465
466    use super::{PathGen, PathItem, PathNode};
467
468    #[test]
469    fn construct_prefix() {
470        let s = PathGen
471            .push(Deck("deck-id".into()))
472            .prefix::<Key>()
473            .to_bytes();
474
475        assert_eq!(&s, b"deck.deck-id.key")
476    }
477
478    #[test]
479    fn construct_escaped_prefix() {
480        let s = PathGen
481            .push(Deck("deck.id".into()))
482            .prefix::<Key>()
483            .to_bytes();
484
485        assert_eq!(&s, b"deck.deck\\.id.key")
486    }
487
488    #[test]
489    fn deconstruct_prefix() {
490        let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
491
492        let s = b"deck.deck-id.key";
493
494        let path = parser.parse(s).unwrap();
495
496        assert_eq!(path, PathGen.push(Deck("deck-id".into())).prefix::<Key>());
497    }
498
499    #[test]
500    fn deconstruct_escaped_prefix() {
501        let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
502
503        let s = b"deck.deck\\.id.key";
504
505        let path = parser.parse(s).unwrap();
506
507        assert_eq!(path, PathGen.push(Deck("deck.id".into())).prefix::<Key>());
508    }
509
510    #[test]
511    fn test_parts() {
512        let string = String::from("hello");
513        let brw: &str = &string;
514
515        test_pathnode::<_, Deck<'_>, _>(Deck(Cow::Borrowed(brw)));
516        test_pathnode::<_, Key, _>(Key(5));
517    }
518
519    #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
520    struct Deck<'a>(Cow<'a, str>);
521    #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
522    struct Key(u8);
523
524    impl<'a> PathItem<'a> for Deck<'a> {
525        type Error = Utf8Error;
526
527        fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
528        where
529            Self: Sized + 'a,
530        {
531            let cow = match bytes {
532                Cow::Owned(bytes) => {
533                    from_utf8(&bytes)?;
534                    Cow::Owned(String::from_utf8(bytes).unwrap())
535                }
536                Cow::Borrowed(bytes) => Cow::Borrowed(from_utf8(bytes)?),
537            };
538
539            Ok(Deck(cow))
540        }
541
542        fn to_bytes(&self) -> Cow<'_, [u8]> {
543            Cow::Borrowed(self.0.as_bytes())
544        }
545    }
546
547    impl<'a> PathItem<'a> for Key {
548        type Error = ParseIntError;
549
550        fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
551        where
552            Self: Sized + 'a,
553        {
554            Ok(Key(String::from_utf8_lossy(&bytes).parse()?))
555        }
556
557        fn to_bytes(&self) -> Cow<'_, [u8]> {
558            Cow::Owned(self.0.to_string().into_bytes())
559        }
560    }
561
562    impl<'a> PathNode<'a> for Deck<'a> {
563        const NAME: &'static [u8] = b"deck";
564    }
565
566    impl PathNode<'_> for Key {
567        const NAME: &'static [u8] = b"key";
568    }
569}