1#![no_std]
2
3extern crate alloc;
4
5use alloc::{
6    borrow::Cow,
7    format,
8    string::{String, ToString},
9    vec::Vec,
10};
11use core::fmt;
12
13#[derive(Clone, Debug, Eq, Hash, PartialEq)]
15pub struct RedoxScheme<'a>(Cow<'a, str>);
16
17impl<'a> RedoxScheme<'a> {
18    pub fn new<S: Into<Cow<'a, str>>>(scheme: S) -> Option<Self> {
20        let scheme = scheme.into();
21        if scheme.contains(&['\0', '/', ':']) {
23            return None;
24        }
25        Some(Self(scheme))
26    }
27}
28
29impl<'a> AsRef<str> for RedoxScheme<'a> {
30    fn as_ref(&self) -> &str {
31        self.0.as_ref()
32    }
33}
34
35impl<'a> fmt::Display for RedoxScheme<'a> {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        write!(f, "{}", self.0)
38    }
39}
40
41#[derive(Clone, Debug, Eq, Hash, PartialEq)]
43pub struct RedoxReference<'a>(Cow<'a, str>);
44
45impl<'a> RedoxReference<'a> {
46    pub fn new<S: Into<Cow<'a, str>>>(reference: S) -> Option<Self> {
48        let reference = reference.into();
49        if reference.contains(&['\0']) {
51            return None;
52        }
53        Some(Self(reference))
54    }
55
56    pub fn join<S: Into<Cow<'a, str>>>(&self, path: S) -> Option<Self> {
61        let path = path.into();
62        if path.starts_with('/') {
63            Self::new(path)
65        } else if path.is_empty() {
66            Self::new(self.0.clone())
68        } else {
69            let mut reference = self.0.clone().into_owned();
71            if !reference.is_empty() && !reference.ends_with('/') {
72                reference.push('/');
73            }
74            reference.push_str(&path);
75            Self::new(reference)
76        }
77    }
78
79    pub fn canonical(&self) -> Option<Self> {
83        let canonical = {
84            let parts = self
85                .0
86                .split('/')
87                .rev()
88                .scan(0, |nskip, part| {
89                    if part == "." {
90                        Some(None)
91                    } else if part == ".." {
92                        *nskip += 1;
93                        Some(None)
94                    } else if *nskip > 0 {
95                        *nskip -= 1;
96                        Some(None)
97                    } else {
98                        Some(Some(part))
99                    }
100                })
101                .filter_map(|x| x)
102                .filter(|x| !x.is_empty())
103                .collect::<Vec<_>>();
104            parts.iter().rev().fold(String::new(), |mut string, &part| {
105                if !string.is_empty() && !string.ends_with('/') {
106                    string.push('/');
107                }
108                string.push_str(part);
109                string
110            })
111        };
112        Self::new(canonical)
113    }
114
115    pub fn is_canon(&self) -> bool {
117        self.0.is_empty()
118            || self
119                .0
120                .split('/')
121                .all(|seg| seg != ".." && seg != "." && seg != "")
122    }
123}
124
125impl<'a> AsRef<str> for RedoxReference<'a> {
126    fn as_ref(&self) -> &str {
127        self.0.as_ref()
128    }
129}
130
131impl<'a> fmt::Display for RedoxReference<'a> {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{}", self.0)
134    }
135}
136
137#[derive(Clone, Debug, Eq, Hash, PartialEq)]
139pub enum RedoxPath<'a> {
140    Standard(RedoxReference<'a>),
142    Legacy(RedoxScheme<'a>, RedoxReference<'a>),
144}
145
146impl<'a> RedoxPath<'a> {
147    pub fn from_absolute(path: &'a str) -> Option<Self> {
151        Some(if path.starts_with('/') {
152            Self::Standard(RedoxReference::new(&path[1..])?)
154        } else {
155            let mut parts = path.splitn(2, ':');
157            let scheme = RedoxScheme::new(parts.next()?)?;
158            let reference = RedoxReference::new(parts.next()?)?;
159            Self::Legacy(scheme, reference)
160        })
161    }
162
163    pub fn join(&self, path: &'a str) -> Option<Self> {
168        if path.starts_with('/') {
169            Self::from_absolute(path)
170        } else {
171            Some(match self {
172                Self::Standard(reference) => Self::Standard(reference.join(path)?),
173                Self::Legacy(scheme, reference) => {
174                    Self::Legacy(scheme.clone(), reference.join(path)?)
175                }
176            })
177        }
178    }
179
180    pub fn canonical(&self) -> Option<Self> {
184        Some(match self {
185            Self::Standard(reference) => Self::Standard(reference.canonical()?),
186            Self::Legacy(scheme, reference) => {
187                Self::Legacy(scheme.clone(), reference.clone())
190            }
191        })
192    }
193
194    pub fn is_canon(&self) -> bool {
200        match self {
201            Self::Standard(reference) => reference.is_canon(),
202            Self::Legacy(_scheme, _reference) => true,
203        }
204    }
205
206    pub fn as_parts(&'a self) -> Option<(RedoxScheme<'a>, RedoxReference<'a>)> {
211        const SCHEME_PREFIX_LENGTH: usize = "scheme/".len();
212
213        if !self.is_canon() {
214            return None;
215        }
216        match self {
217            Self::Standard(reference) => {
218                let mut parts = reference.0.split('/');
220                loop {
221                    match parts.next() {
222                        Some("") => {
223                            }
225                        Some("scheme") => match parts.next() {
226                            Some(scheme_name) => {
227                                let scheme_length = SCHEME_PREFIX_LENGTH + scheme_name.len() + 1;
229                                let remainder = reference.0.get(scheme_length..).unwrap_or("");
230
231                                return Some((
232                                    RedoxScheme(Cow::from(scheme_name)),
233                                    RedoxReference(Cow::from(remainder)),
234                                ));
235                            }
236                            None => {
237                                return Some((
239                                    RedoxScheme(Cow::from("")),
240                                    RedoxReference(Cow::from("")),
241                                ));
242                            }
243                        },
244                        _ => {
245                            return Some((RedoxScheme(Cow::from("file")), reference.clone()));
247                        }
248                    }
249                }
250            }
251            Self::Legacy(scheme, reference) => {
252                Some((scheme.clone(), reference.clone()))
254            }
255        }
256    }
257
258    pub fn matches_scheme(&self, other: &str) -> bool {
260        if let Some((scheme, _)) = self.as_parts() {
261            scheme.0 == other
262        } else {
263            false
264        }
265    }
266
267    pub fn is_scheme_category(&self, category: &str) -> bool {
269        if let Some((scheme, _)) = self.as_parts() {
270            let mut parts = scheme.0.splitn(2, '.');
271            if let Some(cat) = parts.next() {
272                cat == category && parts.next().is_some()
273            } else {
274                false
275            }
276        } else {
277            false
278        }
279    }
280
281    pub fn is_default_scheme(&self) -> bool {
283        self.matches_scheme("file")
284    }
285
286    pub fn is_legacy(&self) -> bool {
288        match self {
289            RedoxPath::Legacy(_, _) => true,
290            _ => false,
291        }
292    }
293
294    pub fn to_standard(&self) -> String {
296        match self {
297            RedoxPath::Standard(reference) => {
298                format!("/{}", reference.0)
299            }
300            RedoxPath::Legacy(scheme, reference) => {
301                format!("/scheme/{}/{}", scheme.0, reference.0)
302            }
303        }
304    }
305
306    pub fn to_standard_canon(&self) -> Option<String> {
309        Some(match self {
310            RedoxPath::Standard(reference) => {
311                format!("/{}", reference.canonical()?.0)
312            }
313            RedoxPath::Legacy(scheme, reference) => canonicalize_using_scheme(
314                scheme.as_ref(),
315                reference.as_ref().trim_start_matches('/'),
316            )?,
317        })
318    }
319}
320
321impl<'a> fmt::Display for RedoxPath<'a> {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        match self {
324            RedoxPath::Standard(reference) => {
325                write!(f, "/{}", reference.0)
326            }
327            RedoxPath::Legacy(scheme, reference) => {
328                write!(f, "{}:{}", scheme.0, reference.0)
329            }
330        }
331    }
332}
333
334pub fn canonicalize_using_cwd<'a>(cwd_opt: Option<&str>, path: &'a str) -> Option<String> {
348    let absolute = match RedoxPath::from_absolute(path) {
349        Some(absolute) => absolute,
350        None => {
351            let cwd = cwd_opt?;
352            let absolute = RedoxPath::from_absolute(cwd)?;
353            absolute.join(path)?
354        }
355    };
356    let canonical = absolute.canonical()?;
357    Some(canonical.to_string())
358}
359
360pub fn canonicalize_to_standard<'a>(cwd_opt: Option<&str>, path: &'a str) -> Option<String> {
363    let absolute = match RedoxPath::from_absolute(path) {
364        Some(absolute) => absolute,
365        None => {
366            let cwd = cwd_opt?;
367            let absolute = RedoxPath::from_absolute(cwd)?;
368            absolute.join(path)?
369        }
370    };
371    absolute.to_standard_canon()
372}
373
374pub fn canonicalize_using_scheme<'a>(scheme: &str, path: &'a str) -> Option<String> {
380    canonicalize_using_cwd(Some(&scheme_path(scheme)?), path)
381}
382
383pub fn scheme_path(name: &str) -> Option<String> {
387    let _ = RedoxScheme::new(name)?;
388    canonicalize_using_cwd(Some("/scheme"), name)
389}
390
391pub fn make_scheme_name(category: &str, detail: &str) -> Option<String> {
395    let name = format!("{}.{}", category, detail);
396    let _ = RedoxScheme::new(&name)?;
397    Some(name)
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    use alloc::{format, string::ToString};
404
405    #[test]
407    fn test_absolute() {
408        let cwd_opt = None;
409        assert_eq!(canonicalize_using_cwd(cwd_opt, "/"), Some("/".to_string()));
410        assert_eq!(
411            canonicalize_using_cwd(cwd_opt, "/file"),
412            Some("/file".to_string())
413        );
414        assert_eq!(
415            canonicalize_using_cwd(cwd_opt, "/folder/file"),
416            Some("/folder/file".to_string())
417        );
418        assert_eq!(
419            canonicalize_using_cwd(cwd_opt, "/folder/../file"),
420            Some("/file".to_string())
421        );
422        assert_eq!(
423            canonicalize_using_cwd(cwd_opt, "/folder/../.."),
424            Some("/".to_string())
425        );
426        assert_eq!(
427            canonicalize_using_cwd(cwd_opt, "/folder/../../../.."),
428            Some("/".to_string())
429        );
430        assert_eq!(
431            canonicalize_using_cwd(cwd_opt, "/.."),
432            Some("/".to_string())
433        );
434    }
435
436    #[test]
438    fn test_new_relative() {
439        let cwd_opt = Some("/scheme/foo");
440        assert_eq!(
441            canonicalize_using_cwd(cwd_opt, "file"),
442            Some("/scheme/foo/file".to_string())
443        );
444        assert_eq!(
445            canonicalize_using_cwd(cwd_opt, "folder/file"),
446            Some("/scheme/foo/folder/file".to_string())
447        );
448        assert_eq!(
449            canonicalize_using_cwd(cwd_opt, "folder/../file"),
450            Some("/scheme/foo/file".to_string())
451        );
452        assert_eq!(
453            canonicalize_using_cwd(cwd_opt, "folder/../.."),
454            Some("/scheme".to_string())
455        );
456        assert_eq!(
457            canonicalize_using_cwd(cwd_opt, "folder/../../../.."),
458            Some("/".to_string())
459        );
460        assert_eq!(
461            canonicalize_using_cwd(cwd_opt, ".."),
462            Some("/scheme".to_string())
463        );
464    }
465
466    #[test]
468    fn test_new_scheme() {
469        let cwd_opt = None;
470        assert_eq!(
471            canonicalize_using_cwd(cwd_opt, "/scheme/bar/"),
472            Some("/scheme/bar".to_string())
473        );
474        assert_eq!(
475            canonicalize_using_cwd(cwd_opt, "/scheme/bar/file"),
476            Some("/scheme/bar/file".to_string())
477        );
478        assert_eq!(
479            canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/file"),
480            Some("/scheme/bar/folder/file".to_string())
481        );
482        assert_eq!(
483            canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../file"),
484            Some("/scheme/bar/file".to_string())
485        );
486        assert_eq!(
487            canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../.."),
488            Some("/scheme".to_string())
489        );
490        assert_eq!(
491            canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../../../.."),
492            Some("/".to_string())
493        );
494        assert_eq!(
495            canonicalize_using_cwd(cwd_opt, "/scheme/bar/.."),
496            Some("/scheme".to_string())
497        );
498
499        assert_eq!(
500            canonicalize_using_scheme("bar", ""),
501            Some("/scheme/bar".to_string())
502        );
503        assert_eq!(
504            canonicalize_using_scheme("bar", "foo"),
505            Some("/scheme/bar/foo".to_string())
506        );
507        assert_eq!(
508            canonicalize_using_scheme("bar", ".."),
509            Some("/scheme".to_string())
510        );
511    }
512
513    #[test]
515    fn test_old_relative() {
516        let cwd_opt = Some("foo:");
517        assert_eq!(
518            canonicalize_using_cwd(cwd_opt, "file"),
519            Some("foo:file".to_string())
520        );
521        assert_eq!(
522            canonicalize_using_cwd(cwd_opt, "folder/file"),
523            Some("foo:folder/file".to_string())
524        );
525        assert_eq!(
526            canonicalize_using_cwd(cwd_opt, "folder/../file"),
527            Some("foo:folder/../file".to_string())
528        );
529        assert_eq!(
530            canonicalize_using_cwd(cwd_opt, "folder/../.."),
531            Some("foo:folder/../..".to_string())
532        );
533        assert_eq!(
534            canonicalize_using_cwd(cwd_opt, "folder/../../../.."),
535            Some("foo:folder/../../../..".to_string())
536        );
537        assert_eq!(
538            canonicalize_using_cwd(cwd_opt, ".."),
539            Some("foo:..".to_string())
540        );
541    }
542
543    #[test]
545    fn test_old_scheme() {
546        let cwd_opt = None;
547        assert_eq!(
548            canonicalize_using_cwd(cwd_opt, "bar:"),
549            Some("bar:".to_string())
550        );
551        assert_eq!(
552            canonicalize_using_cwd(cwd_opt, "bar:file"),
553            Some("bar:file".to_string())
554        );
555        assert_eq!(
556            canonicalize_using_cwd(cwd_opt, "bar:folder/file"),
557            Some("bar:folder/file".to_string())
558        );
559        assert_eq!(
560            canonicalize_using_cwd(cwd_opt, "bar:folder/../file"),
561            Some("bar:folder/../file".to_string())
562        );
563        assert_eq!(
564            canonicalize_using_cwd(cwd_opt, "bar:folder/../.."),
565            Some("bar:folder/../..".to_string())
566        );
567        assert_eq!(
568            canonicalize_using_cwd(cwd_opt, "bar:folder/../../../.."),
569            Some("bar:folder/../../../..".to_string())
570        );
571        assert_eq!(
572            canonicalize_using_cwd(cwd_opt, "bar:.."),
573            Some("bar:..".to_string())
574        );
575    }
576
577    #[test]
579    fn test_orbital_scheme() {
580        for flag_str in &["", "abflrtu"] {
581            for x in &[-1, 0, 1] {
582                for y in &[-1, 0, 1] {
583                    for w in &[0, 1] {
584                        for h in &[0, 1] {
585                            for title in &[
586                                "",
587                                "title",
588                                "title/with/slashes",
589                                "title:with:colons",
590                                "title/../with/../dots/..",
591                            ] {
592                                let path = format!(
593                                    "orbital:{}/{}/{}/{}/{}/{}",
594                                    flag_str, x, y, w, h, title
595                                );
596                                assert_eq!(canonicalize_using_cwd(None, &path), Some(path));
597                            }
598                        }
599                    }
600                }
601            }
602        }
603    }
604
605    #[test]
607    fn test_parts() {
608        for (path, scheme, reference) in &[
609            ("/foo/bar/baz", "file", "foo/bar/baz"),
610            ("/scheme/foo/bar/baz", "foo", "bar/baz"),
611            ("/", "file", ""),
612            ("/bar", "file", "bar"),
613            ("/...", "file", "..."),
614        ] {
615            let redox_path = RedoxPath::from_absolute(path).unwrap();
616            let parts = redox_path.as_parts();
617            assert_eq!(
618                (path, parts),
619                (
620                    path,
621                    Some((
622                        RedoxScheme::new(*scheme).unwrap(),
623                        RedoxReference::new(*reference).unwrap()
624                    ))
625                )
626            );
627            let to_string = format!("/scheme/{scheme}");
628            let joined_path = RedoxPath::from_absolute(&to_string)
629                .unwrap()
630                .join(reference)
631                .unwrap();
632            if path.starts_with("/scheme") {
633                assert_eq!(path, &format!("{joined_path}"));
634            } else {
635                assert_eq!(path, &format!("/{reference}"));
636            }
637        }
638
639        assert_eq!(RedoxPath::from_absolute("not/absolute"), None);
641
642        for path in [
644            "//double/slash",
645            "/ending/in/slash/",
646            "/contains/dot/.",
647            "/contains/dotdot/..",
648        ] {
649            let redox_path = RedoxPath::from_absolute(path).unwrap();
650            let parts = redox_path.as_parts();
651            assert_eq!((path, parts), (path, None));
652        }
653    }
654
655    #[test]
656    fn test_old_scheme_parts() {
657        for (path, scheme, reference) in &[
658            ("foo:bar/baz", "foo", "bar/baz"),
659            ("emptyref:", "emptyref", ""),
660            (":emptyscheme", "", "emptyscheme"),
661        ] {
662            let redox_path = RedoxPath::from_absolute(path).unwrap();
663            let parts = redox_path.as_parts();
664            assert_eq!(
665                (path, parts),
666                (
667                    path,
668                    Some((
669                        RedoxScheme::new(*scheme).unwrap(),
670                        RedoxReference::new(*reference).unwrap()
671                    ))
672                )
673            );
674        }
675
676        assert_eq!(RedoxPath::from_absolute("scheme/withslash:path"), None);
678        assert_eq!(RedoxPath::from_absolute(""), None)
680    }
681
682    #[test]
683    fn test_matches() {
684        assert!(RedoxPath::from_absolute("/scheme/foo")
685            .unwrap()
686            .matches_scheme("foo"));
687        assert!(RedoxPath::from_absolute("/scheme/foo/bar")
688            .unwrap()
689            .matches_scheme("foo"));
690        assert!(!RedoxPath::from_absolute("/scheme/foo")
691            .unwrap()
692            .matches_scheme("bar"));
693        assert!(RedoxPath::from_absolute("foo:")
694            .unwrap()
695            .matches_scheme("foo"));
696        assert!(RedoxPath::from_absolute(
697            &canonicalize_using_cwd(Some("/scheme/foo"), "bar").unwrap()
698        )
699        .unwrap()
700        .matches_scheme("foo"));
701        assert!(
702            RedoxPath::from_absolute(&canonicalize_using_cwd(Some("/foo"), "bar").unwrap())
703                .unwrap()
704                .matches_scheme("file")
705        );
706        assert!(RedoxPath::from_absolute(
707            &canonicalize_using_cwd(Some("/scheme"), "foo/bar").unwrap()
708        )
709        .unwrap()
710        .matches_scheme("foo"));
711        assert!(RedoxPath::from_absolute("foo:/bar")
712            .unwrap()
713            .matches_scheme("foo"));
714        assert!(!RedoxPath::from_absolute("foo:/bar")
715            .unwrap()
716            .matches_scheme("bar"));
717        assert!(RedoxPath::from_absolute("/scheme/file")
718            .unwrap()
719            .is_default_scheme());
720        assert!(!RedoxPath::from_absolute("/scheme/foo")
721            .unwrap()
722            .is_default_scheme());
723        assert!(RedoxPath::from_absolute("file:bar")
724            .unwrap()
725            .is_default_scheme());
726        assert!(RedoxPath::from_absolute("file:")
727            .unwrap()
728            .is_default_scheme());
729        assert!(!RedoxPath::from_absolute("foo:bar")
730            .unwrap()
731            .is_default_scheme());
732        assert!(RedoxPath::from_absolute("foo:bar").unwrap().is_legacy());
733        assert!(!RedoxPath::from_absolute("/foo/bar").unwrap().is_legacy());
734    }
735
736    #[test]
737    fn test_to_standard() {
738        assert_eq!(
739            &RedoxPath::from_absolute("foo:bar").unwrap().to_standard(),
740            "/scheme/foo/bar"
741        );
742        assert_eq!(
743            &RedoxPath::from_absolute("file:bar").unwrap().to_standard(),
744            "/scheme/file/bar"
745        );
746        assert_eq!(
747            &RedoxPath::from_absolute("/scheme/foo/bar")
748                .unwrap()
749                .to_standard(),
750            "/scheme/foo/bar"
751        );
752        assert_eq!(
753            &RedoxPath::from_absolute("/foo/bar").unwrap().to_standard(),
754            "/foo/bar"
755        );
756        assert_eq!(
757            &RedoxPath::from_absolute("foo:bar/../bar2")
758                .unwrap()
759                .to_standard_canon()
760                .unwrap(),
761            "/scheme/foo/bar2"
762        );
763        assert_eq!(
764            &RedoxPath::from_absolute("file:bar/./../bar2")
765                .unwrap()
766                .to_standard_canon()
767                .unwrap(),
768            "/scheme/file/bar2"
769        );
770        assert_eq!(
771            &RedoxPath::from_absolute("/scheme/file/bar/./../../foo/bar")
772                .unwrap()
773                .to_standard_canon()
774                .unwrap(),
775            "/scheme/foo/bar"
776        );
777        assert_eq!(
778            &RedoxPath::from_absolute("/foo/bar")
779                .unwrap()
780                .to_standard_canon()
781                .unwrap(),
782            "/foo/bar"
783        );
784        assert_eq!(
785            &canonicalize_to_standard(None, "/scheme/foo/bar").unwrap(),
786            "/scheme/foo/bar"
787        );
788        assert_eq!(
789            &canonicalize_to_standard(None, "foo:bar").unwrap(),
790            "/scheme/foo/bar"
791        );
792        assert_eq!(
793            &canonicalize_to_standard(None, "foo:bar/../..").unwrap(),
794            "/scheme"
795        );
796        assert_eq!(
797            &canonicalize_to_standard(None, "/scheme/foo/bar/..").unwrap(),
798            "/scheme/foo"
799        );
800        assert_eq!(
801            &canonicalize_to_standard(None, "foo:bar/bar2/..").unwrap(),
802            "/scheme/foo/bar"
803        );
804    }
805
806    #[test]
807    fn test_scheme_path() {
808        assert_eq!(scheme_path("foo"), Some("/scheme/foo".to_string()));
809        assert_eq!(scheme_path(""), Some("/scheme".to_string()));
810        assert_eq!(scheme_path("/foo"), None);
811        assert_eq!(scheme_path("foo/bar"), None);
812        assert_eq!(scheme_path("foo:"), None);
813    }
814
815    #[test]
816    fn test_category() {
817        assert_eq!(make_scheme_name("foo", "bar"), Some("foo.bar".to_string()));
818        assert_eq!(
819            RedoxPath::from_absolute(
820                &scheme_path(&make_scheme_name("foo", "bar").unwrap()).unwrap()
821            )
822            .unwrap(),
823            RedoxPath::Standard(RedoxReference::new("scheme/foo.bar").unwrap())
824        );
825        assert_eq!(make_scheme_name("foo", "/bar"), None);
826        assert_eq!(make_scheme_name("foo", ":bar"), None);
827        assert!(RedoxPath::from_absolute(
828            &scheme_path(&make_scheme_name("foo", "bar").unwrap()).unwrap()
829        )
830        .unwrap()
831        .is_scheme_category("foo"));
832        assert!(RedoxPath::from_absolute("/scheme/foo.bar/bar2")
833            .unwrap()
834            .is_scheme_category("foo"));
835        assert!(!RedoxPath::from_absolute("/scheme/foo/bar")
836            .unwrap()
837            .is_scheme_category("foo"));
838        assert!(!RedoxPath::from_absolute("/foo.bar/bar2")
839            .unwrap()
840            .is_scheme_category("foo"));
841    }
842}