flams_router_base/
uris.rs

1use flams_ontology::{
2    languages::Language,
3    uris::{
4        ArchiveId, ArchiveURI, DocumentElementURI, DocumentURI, ModuleURI, PathURI, SymbolURI, URI,
5    },
6};
7use std::str::FromStr;
8
9macro_rules! charstr {
10    ($c:ident) => {
11        const_str::concat!($c::SEPARATOR)
12    };
13}
14
15#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16pub enum URIKind {
17    Full,
18    Rel,
19    Archive,
20    Path,
21    Document,
22    DocumentElement,
23    Module,
24    Declaration,
25}
26
27#[derive(Clone)]
28pub enum SymURIComponents {
29    Uri(SymbolURI),
30    Comps {
31        a: ArchiveId,
32        p: Option<String>,
33        m: String,
34        s: String,
35    },
36}
37
38impl SymURIComponents {
39    pub fn into_args<
40        R,
41        F: FnOnce(
42            Option<SymbolURI>,
43            Option<ArchiveId>,
44            Option<String>,
45            Option<Language>,
46            Option<String>,
47            Option<String>,
48        ) -> R,
49    >(
50        self,
51        f: F,
52    ) -> R {
53        match self {
54            Self::Uri(uri) => f(Some(uri), None, None, None, None, None),
55            Self::Comps { a, p, m, s } => f(None, Some(a), p, None, Some(m), Some(s)),
56        }
57    }
58}
59
60impl
61    TryFrom<(
62        Option<SymbolURI>,
63        Option<ArchiveId>,
64        Option<String>,
65        Option<String>,
66        Option<String>,
67    )> for SymURIComponents
68{
69    type Error = ();
70    fn try_from(
71        (uri, a, p, m, s): (
72            Option<SymbolURI>,
73            Option<ArchiveId>,
74            Option<String>,
75            Option<String>,
76            Option<String>,
77        ),
78    ) -> Result<Self, ()> {
79        if let Some(uri) = uri {
80            return if a.is_none() && p.is_none() && m.is_none() && s.is_none() {
81                Ok(Self::Uri(uri))
82            } else {
83                Err(())
84            };
85        }
86        a.map_or_else(
87            || Err(()),
88            |a| match (m, s) {
89                (Some(m), Some(s)) => Ok(Self::Comps { a, p, m, s }),
90                _ => Err(()),
91            },
92        )
93    }
94}
95
96#[derive(Clone)]
97pub enum DocURIComponents {
98    Uri(DocumentURI),
99    RelPath(ArchiveId, String),
100    Comps {
101        a: ArchiveId,
102        p: Option<String>,
103        l: Option<Language>,
104        d: String,
105    },
106}
107impl DocURIComponents {
108    pub fn into_args<
109        R,
110        F: FnOnce(
111            Option<DocumentURI>,
112            Option<String>,
113            Option<ArchiveId>,
114            Option<String>,
115            Option<Language>,
116            Option<String>,
117        ) -> R,
118    >(
119        self,
120        f: F,
121    ) -> R {
122        match self {
123            Self::Uri(uri) => f(Some(uri), None, None, None, None, None),
124            Self::RelPath(a, rp) => f(None, Some(rp), Some(a), None, None, None),
125            Self::Comps { a, p, l, d } => f(None, None, Some(a), p, l, Some(d)),
126        }
127    }
128}
129
130impl
131    TryFrom<(
132        Option<DocumentURI>,
133        Option<String>,
134        Option<ArchiveId>,
135        Option<String>,
136        Option<Language>,
137        Option<String>,
138    )> for DocURIComponents
139{
140    type Error = ();
141    fn try_from(
142        (uri, rp, a, p, l, d): (
143            Option<DocumentURI>,
144            Option<String>,
145            Option<ArchiveId>,
146            Option<String>,
147            Option<Language>,
148            Option<String>,
149        ),
150    ) -> Result<Self, ()> {
151        if let Some(uri) = uri {
152            return if rp.is_none() && a.is_none() && p.is_none() && l.is_none() && d.is_none() {
153                Ok(Self::Uri(uri))
154            } else {
155                Err(())
156            };
157        }
158        a.map_or_else(
159            || Err(()),
160            |a| {
161                if let Some(rp) = rp {
162                    if p.is_none() && l.is_none() && d.is_none() {
163                        Ok(Self::RelPath(a, rp))
164                    } else {
165                        Err(())
166                    }
167                } else if let Some(d) = d {
168                    Ok(Self::Comps { a, p, l, d })
169                } else {
170                    Err(())
171                }
172            },
173        )
174    }
175}
176
177#[derive(Clone)]
178pub enum URIComponents {
179    Uri(URI),
180    RelPath(ArchiveId, String),
181    DocComps {
182        a: ArchiveId,
183        p: Option<String>,
184        l: Option<Language>,
185        d: String,
186    },
187    ElemComps {
188        a: ArchiveId,
189        p: Option<String>,
190        l: Option<Language>,
191        d: String,
192        e: String,
193    },
194    ModComps {
195        a: ArchiveId,
196        p: Option<String>,
197        m: String,
198    },
199    SymComps {
200        a: ArchiveId,
201        p: Option<String>,
202        m: String,
203        s: String,
204    },
205}
206impl From<DocURIComponents> for URIComponents {
207    fn from(value: DocURIComponents) -> Self {
208        match value {
209            DocURIComponents::Uri(u) => Self::Uri(URI::Narrative(u.into())),
210            DocURIComponents::RelPath(a, rp) => Self::RelPath(a, rp),
211            DocURIComponents::Comps { a, p, l, d } => Self::DocComps { a, p, l, d },
212        }
213    }
214}
215
216impl URIComponents {
217    pub fn into_args<
218        R,
219        F: FnOnce(
220            Option<URI>,
221            Option<String>,
222            Option<ArchiveId>,
223            Option<String>,
224            Option<Language>,
225            Option<String>,
226            Option<String>,
227            Option<String>,
228            Option<String>,
229            Option<URI>,
230        ) -> R,
231    >(
232        self,
233        f: F,
234    ) -> R {
235        match self {
236            Self::Uri(uri) => f(
237                Some(uri),
238                None,
239                None,
240                None,
241                None,
242                None,
243                None,
244                None,
245                None,
246                None,
247            ),
248            Self::RelPath(a, rp) => f(
249                None,
250                Some(rp),
251                Some(a),
252                None,
253                None,
254                None,
255                None,
256                None,
257                None,
258                None,
259            ),
260            Self::DocComps { a, p, l, d } => {
261                f(None, None, Some(a), p, l, Some(d), None, None, None, None)
262            }
263            Self::ElemComps { a, p, l, d, e } => f(
264                None,
265                None,
266                Some(a),
267                p,
268                l,
269                Some(d),
270                Some(e),
271                None,
272                None,
273                None,
274            ),
275            Self::ModComps { a, p, m } => f(
276                None,
277                None,
278                Some(a),
279                p,
280                None,
281                None,
282                None,
283                Some(m),
284                None,
285                None,
286            ),
287            Self::SymComps { a, p, m, s } => f(
288                None,
289                None,
290                Some(a),
291                p,
292                None,
293                None,
294                None,
295                Some(m),
296                Some(s),
297                None,
298            ),
299        }
300    }
301}
302
303#[allow(clippy::many_single_char_names)]
304impl
305    TryFrom<(
306        Option<URI>,
307        Option<String>,
308        Option<ArchiveId>,
309        Option<String>,
310        Option<Language>,
311        Option<String>,
312        Option<String>,
313        Option<String>,
314        Option<String>,
315    )> for URIComponents
316{
317    type Error = ();
318    fn try_from(
319        (uri, rp, a, p, l, d, e, m, s): (
320            Option<URI>,
321            Option<String>,
322            Option<ArchiveId>,
323            Option<String>,
324            Option<Language>,
325            Option<String>,
326            Option<String>,
327            Option<String>,
328            Option<String>,
329        ),
330    ) -> Result<Self, ()> {
331        if let Some(uri) = uri {
332            return if rp.is_none()
333                && a.is_none()
334                && p.is_none()
335                && l.is_none()
336                && d.is_none()
337                && e.is_none()
338                && m.is_none()
339                && s.is_none()
340            {
341                Ok(Self::Uri(uri))
342            } else {
343                Err(())
344            };
345        }
346        a.map_or_else(
347            || Err(()),
348            |a| {
349                if let Some(rp) = rp {
350                    if p.is_none() && l.is_none() && d.is_none() && m.is_none() && s.is_none() {
351                        Ok(Self::RelPath(a, rp))
352                    } else {
353                        Err(())
354                    }
355                } else if let Some(d) = d {
356                    if e.is_none() && m.is_none() && s.is_none() {
357                        Ok(Self::DocComps { a, p, l, d })
358                    } else if let Some(e) = e {
359                        if m.is_none() && s.is_none() {
360                            Ok(Self::ElemComps { a, p, l, d, e })
361                        } else {
362                            Err(())
363                        }
364                    } else {
365                        Err(())
366                    }
367                } else if let Some(m) = m {
368                    if d.is_none() && e.is_none() && s.is_none() && l.is_none() {
369                        Ok(Self::ModComps { a, p, m })
370                    } else if let Some(s) = s {
371                        if d.is_none() && e.is_none() && l.is_none() {
372                            Ok(Self::SymComps { a, p, m, s })
373                        } else {
374                            Err(())
375                        }
376                    } else {
377                        Err(())
378                    }
379                } else {
380                    Err(())
381                }
382            },
383        )
384    }
385}
386
387pub trait URIComponentsTrait {
388    fn get(&self, key: &str) -> Option<&str>;
389    fn get_string(&self, key: &str) -> Option<String>;
390
391    fn kind(&self) -> Option<URIKind>;
392    fn as_doc(&self) -> Option<DocURIComponents> {
393        if let Some(uri) = self.get("uri") {
394            return DocumentURI::from_str(uri).ok().map(DocURIComponents::Uri);
395        }
396        let a = self.get(charstr!(ArchiveURI)).map(ArchiveId::new)?;
397        if let Some(rp) = self.get_string("rp") {
398            if self.get(charstr!(DocumentURI)).is_none()
399                && self.get(charstr!(DocumentElementURI)).is_none()
400                && self.get(charstr!(ModuleURI)).is_none()
401                && self.get(charstr!(SymbolURI)).is_none()
402            {
403                Some(DocURIComponents::RelPath(a, rp))
404            } else {
405                None
406            }
407        } else if self.get(charstr!(DocumentElementURI)).is_none()
408            && self.get(charstr!(ModuleURI)).is_none()
409            && self.get(charstr!(SymbolURI)).is_none()
410        {
411            let p = self.get_string(charstr!(PathURI));
412            let l = self.get("l").and_then(|s| Language::from_str(s).ok());
413            let d = self.get_string("d")?;
414            Some(DocURIComponents::Comps { a, p, l, d })
415        } else {
416            None
417        }
418    }
419    fn as_comps(&self) -> Option<URIComponents> {
420        if let Some(uri) = self.get("uri") {
421            return URI::from_str(uri).ok().map(URIComponents::Uri);
422        }
423        let a = self.get(charstr!(ArchiveURI)).map(ArchiveId::new)?;
424        if let Some(rp) = self.get_string("rp") {
425            return if self.get(charstr!(DocumentURI)).is_none()
426                && self.get(charstr!(DocumentElementURI)).is_none()
427                && self.get(charstr!(ModuleURI)).is_none()
428                && self.get(charstr!(SymbolURI)).is_none()
429            {
430                Some(URIComponents::RelPath(a, rp))
431            } else {
432                None
433            };
434        }
435        if let Some(e) = self.get(charstr!(DocumentElementURI)) {
436            let d = self.get(charstr!(DocumentURI))?;
437            return if self.get(charstr!(ModuleURI)).is_none()
438                && self.get(charstr!(SymbolURI)).is_none()
439            {
440                Some(URIComponents::ElemComps {
441                    a,
442                    p: self.get(charstr!(PathURI)).map(ToString::to_string),
443                    l: self.get("l").and_then(|s| Language::from_str(s).ok()),
444                    d: d.to_string(),
445                    e: e.to_string(),
446                })
447            } else {
448                None
449            };
450        }
451        if let Some(d) = self.get(charstr!(DocumentURI)) {
452            return if self.get(charstr!(ModuleURI)).is_none()
453                && self.get(charstr!(SymbolURI)).is_none()
454            {
455                Some(URIComponents::DocComps {
456                    a,
457                    p: self.get(charstr!(PathURI)).map(ToString::to_string),
458                    l: self.get("l").and_then(|s| Language::from_str(s).ok()),
459                    d: d.to_string(),
460                })
461            } else {
462                None
463            };
464        }
465        if let Some(s) = self.get(charstr!(SymbolURI)) {
466            let m = self.get(charstr!(ModuleURI))?;
467            return if self.get(charstr!(DocumentURI)).is_none()
468                && self.get(charstr!(DocumentElementURI)).is_none()
469            {
470                Some(URIComponents::SymComps {
471                    a,
472                    p: self.get(charstr!(PathURI)).map(ToString::to_string),
473                    m: m.to_string(),
474                    s: s.to_string(),
475                })
476            } else {
477                None
478            };
479        }
480        if let Some(m) = self.get(charstr!(ModuleURI)) {
481            return if self.get(charstr!(DocumentURI)).is_none()
482                && self.get(charstr!(DocumentElementURI)).is_none()
483            {
484                Some(URIComponents::ModComps {
485                    a,
486                    p: self.get(charstr!(PathURI)).map(ToString::to_string),
487                    m: m.to_string(),
488                })
489            } else {
490                None
491            };
492        }
493        None
494    }
495
496    #[cfg(feature = "ssr")]
497    fn parse(&self) -> Option<URI> {
498        if let Some(uri) = self.get("uri") {
499            return URI::from_str(uri).ok();
500        }
501        let a = ArchiveId::new(self.get(charstr!(ArchiveURI))?);
502        if let Some(rp) = self.get("rp") {
503            return from_archive_relpath(&a, rp).map(|r| URI::Narrative(r.into()));
504        }
505        todo!()
506    }
507}
508
509impl URIComponentsTrait for leptos_router::params::ParamsMap {
510    #[inline]
511    fn get(&self, key: &str) -> Option<&str> {
512        self.get_str(key)
513    }
514    #[inline]
515    fn get_string(&self, key: &str) -> Option<String> {
516        self.get(key)
517    }
518    fn kind(&self) -> Option<URIKind> {
519        if self.get("uri").is_some() {
520            return Some(URIKind::Full);
521        }
522        if self.get("rp").is_some() {
523            return Some(URIKind::Rel);
524        }
525        self.get(charstr!(ArchiveURI))?;
526        if self.get(charstr!(DocumentURI)).is_some() {
527            if self.get(charstr!(ModuleURI)).is_some() || self.get(charstr!(SymbolURI)).is_some() {
528                return None;
529            }
530            if self.get(charstr!(DocumentElementURI)).is_some() {
531                Some(URIKind::DocumentElement)
532            } else {
533                Some(URIKind::Document)
534            }
535        } else if self.get(charstr!(ModuleURI)).is_some() {
536            if self.get(charstr!(DocumentElementURI)).is_some() {
537                return None;
538            }
539            if self.get(charstr!(SymbolURI)).is_some() {
540                Some(URIKind::Declaration)
541            } else {
542                Some(URIKind::Module)
543            }
544        } else if self.get(charstr!(PathURI)).is_some() {
545            Some(URIKind::Path)
546        } else {
547            Some(URIKind::Archive)
548        }
549    }
550}
551
552#[cfg(feature = "ssr")]
553mod ssr {
554    use super::{DocURIComponents, SymURIComponents, URIComponents};
555    use flams_ontology::{
556        languages::Language,
557        uris::{
558            ArchiveId, DocumentElementURI, DocumentURI, ModuleURI, Name, SymbolURI, URI,
559            URIRefTrait,
560        },
561    };
562    use flams_system::backend::{Backend, GlobalBackend};
563    use std::str::FromStr;
564
565    impl SymURIComponents {
566        #[must_use]
567        pub fn parse(self) -> Option<SymbolURI> {
568            match self {
569                Self::Uri(uri) => Some(uri),
570                Self::Comps { a, p, m, s } => get_sym_uri(&a, p, &m, &s),
571            }
572        }
573    }
574
575    impl DocURIComponents {
576        #[must_use]
577        pub fn parse(self) -> Option<DocumentURI> {
578            match self {
579                Self::Uri(uri) => Some(uri),
580                Self::RelPath(a, rp) => from_archive_relpath(&a, &rp),
581                Self::Comps { a, p, l, d } => get_doc_uri(
582                    &a,
583                    p.map(|p| Name::from_str(&p).unwrap_or_else(|_| unreachable!())),
584                    l.unwrap_or_default(),
585                    Name::from_str(&d).unwrap_or_else(|_| unreachable!()),
586                ),
587            }
588        }
589    }
590
591    impl URIComponents {
592        #[must_use]
593        pub fn parse(self) -> Option<URI> {
594            match self {
595                Self::Uri(uri) => Some(uri),
596                Self::RelPath(a, rp) => {
597                    from_archive_relpath(&a, &rp).map(|d| URI::Narrative(d.into()))
598                }
599                Self::DocComps { a, p, l, d } => get_doc_uri(
600                    &a,
601                    p.map(|p| Name::from_str(&p).unwrap_or_else(|_| unreachable!())),
602                    l.unwrap_or_default(),
603                    Name::from_str(&d).unwrap_or_else(|_| unreachable!()),
604                )
605                .map(|d| URI::Narrative(d.into())),
606                Self::ElemComps { a, p, l, d, e } => {
607                    get_elem_uri(&a, p, l, &d, &e).map(|e| URI::Narrative(e.into()))
608                }
609                Self::ModComps { a, p, m } => {
610                    get_mod_uri(&a, p, &m).map(|m| URI::Content(m.into()))
611                }
612                Self::SymComps { a, p, m, s } => {
613                    get_sym_uri(&a, p, &m, &s).map(|s| URI::Content(s.into()))
614                }
615            }
616        }
617    }
618
619    #[must_use]
620    pub fn from_archive_relpath(a: &ArchiveId, rp: &str) -> Option<DocumentURI> {
621        let (p, n) = if let Some((p, n)) = rp.rsplit_once('/') {
622            (
623                Some(Name::from_str(p).unwrap_or_else(|_| unreachable!())),
624                n,
625            )
626        } else {
627            (None, rp)
628        };
629        let (n, l) = if let Some((n, l)) = n.rsplit_once('.') {
630            Language::from_str(l).map_or_else(
631                |()| {
632                    n.rsplit_once('.').map_or_else(
633                        || {
634                            (
635                                Name::from_str(n).unwrap_or_else(|_| unreachable!()),
636                                Language::default(),
637                            )
638                        },
639                        |(n, l)| {
640                            (
641                                Name::from_str(n).unwrap_or_else(|_| unreachable!()),
642                                Language::from_str(l).unwrap_or_default(),
643                            )
644                        },
645                    )
646                },
647                |l| (Name::from_str(n).unwrap_or_else(|_| unreachable!()), l),
648            )
649        } else {
650            (
651                Name::from_str(n).unwrap_or_else(|_| unreachable!()),
652                Language::default(),
653            )
654        };
655        get_doc_uri(a, p, l, n)
656    }
657
658    #[must_use]
659    pub fn get_doc_uri(
660        a: &ArchiveId,
661        p: Option<Name>,
662        l: Language,
663        d: Name,
664    ) -> Option<DocumentURI> {
665        let a = GlobalBackend::get().with_archive(a, |a| a.map(|a| a.uri().owned()))?;
666        let p = if let Some(p) = p { a % p } else { a.into() };
667        Some(p & (d, l))
668    }
669
670    #[must_use]
671    #[allow(clippy::many_single_char_names)]
672    pub fn get_elem_uri(
673        a: &ArchiveId,
674        p: Option<String>,
675        l: Option<Language>,
676        d: &str,
677        e: &str,
678    ) -> Option<DocumentElementURI> {
679        get_doc_uri(
680            a,
681            p.map(|p| Name::from_str(&p).ok())?,
682            l.unwrap_or_default(),
683            Name::from_str(d).ok()?,
684        )
685        .and_then(|d| (d & e).ok())
686    }
687
688    #[must_use]
689    #[allow(clippy::many_single_char_names)]
690    pub fn get_mod_uri(a: &ArchiveId, p: Option<String>, m: &str) -> Option<ModuleURI> {
691        let a = GlobalBackend::get().with_archive(a, |a| a.map(|a| a.uri().owned()))?;
692        let p = if let Some(p) = p {
693            a % Name::from_str(&p).ok()?
694        } else {
695            a.into()
696        };
697        (p | m).ok()
698    }
699
700    #[must_use]
701    #[allow(clippy::many_single_char_names)]
702    pub fn get_sym_uri(a: &ArchiveId, p: Option<String>, m: &str, s: &str) -> Option<SymbolURI> {
703        get_mod_uri(a, p, m).and_then(|m| (m | s).ok())
704    }
705}
706
707#[cfg(feature = "ssr")]
708pub use ssr::*;