uriparse/
scheme.rs

1#![allow(clippy::string_lit_as_bytes)]
2
3//! Scheme Component
4//!
5//! See [[RFC3986, Section 3.5](https://tools.ietf.org/html/rfc3986#section-3.5)]. For a list of
6//! the listed schemes, see
7//! [iana.org](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml).
8
9use fnv::FnvBuildHasher;
10use lazy_static::lazy_static;
11use std::borrow::Cow;
12use std::collections::HashMap;
13use std::convert::{Infallible, TryFrom};
14use std::error::Error;
15use std::fmt::{self, Display, Formatter};
16use std::hash::{Hash, Hasher};
17use std::str;
18
19use crate::utility::normalize_string;
20
21/// The length of the longest currently registered scheme. This is used internally for parsing. Make
22/// sure to check this whenever adding a new scheme.
23const MAX_REGISTERED_SCHEME_LENGTH: usize = 36;
24
25/// The number of registered schemes. Make sure to update this whenever adding a new scheme.
26const NUMBER_OF_SCHEMES: usize = 304;
27
28/// A map of byte characters that determines if a character is a valid scheme character.
29#[rustfmt::skip]
30const SCHEME_CHAR_MAP: [u8; 256] = [
31 // 0     1     2     3     4     5     6     7     8     9     A     B     C     D     E     F
32    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 0
33    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 1
34    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, b'+',    0, b'-', b'.',    0, // 2
35 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9',    0,    0,    0,    0,    0,    0, // 3
36    0, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 4
37 b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z',    0,    0,    0,    0,    0, // 5
38    0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', // 6
39 b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',    0,    0,    0,    0,    0, // 7
40    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 8
41    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // 9
42    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // A
43    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // B
44    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // C
45    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // D
46    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // E
47    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0, // F
48];
49
50macro_rules! schemes {
51    (
52        $(
53            ($variant:ident, $name:expr, $status:expr);
54        )+
55    ) => {
56        lazy_static! {
57            /// An immutable hashmap mapping scheme names to their corresponding [`Scheme`]
58            /// variants.
59            static ref SCHEME_NAME_MAP: HashMap<&'static [u8], Scheme<'static>, FnvBuildHasher> = {
60                let mut map = HashMap::with_capacity_and_hasher(
61                    NUMBER_OF_SCHEMES,
62                    FnvBuildHasher::default()
63                );
64
65            $(
66                assert!(map.insert($name.as_bytes(), Scheme::$variant).is_none());
67            )+
68
69                map
70            };
71        }
72
73        /// The scheme component as defined in
74        /// [[RFC3986, Section 3.5](https://tools.ietf.org/html/rfc3986#section-3.5)]. The schemes
75        /// listed here come from
76        /// [iana.org](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml). Any scheme
77        /// not listed there is considered unregistered and will be contained in
78        /// [`Scheme::UnregisteredScheme`].
79        ///
80        /// An unregistered scheme is case-insensitive. Furthermore, percent-encoding is not allowed
81        /// in schemes.
82        #[derive(Clone, Debug, Eq, Hash, PartialEq)]
83        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84        #[non_exhaustive]
85        pub enum Scheme<'scheme> {
86        $(
87            $variant,
88        )+
89            Unregistered(UnregisteredScheme<'scheme>)
90        }
91
92        impl<'scheme> Scheme<'scheme> {
93            /// Returns a new scheme which is identical but has a lifetime tied to this scheme.
94            pub fn as_borrowed(&self) -> Scheme {
95                use self::Scheme::*;
96
97                match self {
98                $(
99                    $variant => $variant,
100                )+
101                    Unregistered(scheme) => Unregistered(scheme.as_borrowed())
102                }
103            }
104
105            /// Returns a `str` representation of the scheme.
106            ///
107            /// The case of the scheme will be lowercase if it was a registered scheme. Otherwise,
108            /// the string representation will be exactly that of the original string including
109            /// case-sensitivity.
110            ///
111            /// # Examples
112            ///
113            /// ```
114            /// use std::convert::TryFrom;
115            ///
116            /// use uriparse::Scheme;
117            ///
118            /// assert_eq!(Scheme::HTTP.as_str(), "http");
119            ///
120            /// let scheme = Scheme::try_from("TEST-scheme").unwrap();
121            /// assert_eq!(scheme.as_str(), "TEST-scheme");
122            /// ```
123            pub fn as_str(&self) -> &str {
124                use self::Scheme::*;
125
126                match self {
127                $(
128                    $variant => $name,
129                )+
130                    Unregistered(scheme) => scheme.as_str()
131                }
132            }
133
134            /// Converts the [`Scheme`] into an owned copy.
135            ///
136            /// If you construct the scheme from a source with a non-static lifetime, you may run
137            /// into lifetime problems due to the way it is designed. Calling this function will
138            /// ensure that the returned value has a static lifetime.
139            ///
140            /// This is different from just cloning. Cloning the scheme will just copy the
141            /// references (in the case of an unregistered scheme), and thus the lifetime will
142            /// remain the same.
143            pub fn into_owned(self) -> Scheme<'static> {
144                use self::Scheme::*;
145
146                match self {
147                $(
148                    $variant => $variant,
149                )+
150                    Unregistered(scheme) => Unregistered(scheme.into_owned())
151                }
152            }
153
154            /// Returns the registration status of the scheme.
155            ///
156            /// # Examples
157            ///
158            /// ```
159            /// use uriparse::{Scheme, SchemeStatus};
160            ///
161            /// assert_eq!(Scheme::HTTP.status(), SchemeStatus::Permanent);
162            /// ```
163            pub fn status(&self) -> SchemeStatus {
164                use self::Scheme::*;
165
166                match self {
167                $(
168                    $variant => $status,
169                )+
170                    Unregistered(_) => SchemeStatus::Unregistered
171                }
172            }
173        }
174
175        /// Parses the scheme from the given byte string.
176        pub(crate) fn parse_scheme(value: &[u8]) -> Result<(Scheme, &[u8]), SchemeError> {
177            fn unregistered_scheme(value: &[u8], normalized: bool) -> Scheme {
178                // Unsafe: The loop below makes sure the byte string is valid ASCII-US.
179                let scheme = unsafe { str::from_utf8_unchecked(value) };
180                Scheme::Unregistered(UnregisteredScheme{
181                    normalized,
182                    scheme:Cow::from(scheme)
183                })
184            }
185
186            if !value.iter().next().ok_or(SchemeError::Empty)?.is_ascii_alphabetic() {
187                return Err(SchemeError::StartsWithNonAlphabetic);
188            }
189
190            let mut end_index = 0;
191            let mut lowercase_scheme = [0; MAX_REGISTERED_SCHEME_LENGTH];
192            let mut normalized = true;
193
194            for &byte in value.iter() {
195                match SCHEME_CHAR_MAP[byte as usize] {
196                    0 if byte == b':' => break,
197                    0 => return Err(SchemeError::InvalidCharacter),
198                    _ => {
199                        if byte >= b'A' && byte <= b'Z' {
200                            normalized = false;
201                        }
202
203                        if end_index + 1 < MAX_REGISTERED_SCHEME_LENGTH {
204                            lowercase_scheme[end_index] = byte.to_ascii_lowercase();
205                        }
206
207                        end_index += 1;
208                    }
209                }
210            }
211
212            let (value, rest) = value.split_at(end_index);
213
214            // It is important to make sure that [`MAX_REGISTERED_SCHEME_LENGTH`] is correctly
215            // maintained, or registered schemes may be set as unregistered.
216
217            if end_index > MAX_REGISTERED_SCHEME_LENGTH {
218                return Ok((unregistered_scheme(value, normalized), rest));
219            }
220
221            let scheme = SCHEME_NAME_MAP
222                .get(&lowercase_scheme[..end_index])
223                .cloned()
224                .unwrap_or_else(|| unregistered_scheme(value, normalized));
225
226            Ok((scheme, rest))
227        }
228    }
229}
230
231impl Scheme<'_> {
232    /// Returns whether the scheme is normalized.
233    ///
234    /// A normalized scheme will be all lowercase. All standardized schemes are always considered
235    /// normalized regardless of what source they were parsed from.
236    ///
237    /// This function returns in constant-time.
238    ///
239    /// # Examples
240    ///
241    /// ```
242    /// use std::convert::TryFrom;
243    ///
244    /// use uriparse::Scheme;
245    ///
246    /// let scheme = Scheme::try_from("http").unwrap();
247    /// assert!(scheme.is_normalized());
248    ///
249    /// let scheme = Scheme::try_from("HTTP").unwrap();
250    /// assert!(scheme.is_normalized());
251    ///
252    /// let mut scheme = Scheme::try_from("MyScHeMe").unwrap();
253    /// assert!(!scheme.is_normalized());
254    /// scheme.normalize();
255    /// assert!(scheme.is_normalized());
256    /// ```
257    pub fn is_normalized(&self) -> bool {
258        match self {
259            Scheme::Unregistered(scheme) => scheme.is_normalized(),
260            _ => true,
261        }
262    }
263
264    /// Normalizes the scheme so that it is all lowercase.
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// use std::convert::TryFrom;
270    ///
271    /// use uriparse::Scheme;
272    ///
273    /// let mut scheme = Scheme::try_from("http").unwrap();
274    /// scheme.normalize();
275    /// assert_eq!(scheme, "http");
276    ///
277    /// let mut scheme = Scheme::try_from("MyScHeMe").unwrap();
278    /// scheme.normalize();
279    /// assert_eq!(scheme, "myscheme");
280    /// ```
281    pub fn normalize(&mut self) {
282        if let Scheme::Unregistered(scheme) = self {
283            scheme.normalize();
284        }
285    }
286}
287
288impl AsRef<[u8]> for Scheme<'_> {
289    fn as_ref(&self) -> &[u8] {
290        self.as_str().as_bytes()
291    }
292}
293
294impl AsRef<str> for Scheme<'_> {
295    fn as_ref(&self) -> &str {
296        self.as_str()
297    }
298}
299
300impl Display for Scheme<'_> {
301    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
302        formatter.write_str(self.as_str())
303    }
304}
305
306impl<'scheme> From<Scheme<'scheme>> for String {
307    fn from(value: Scheme<'scheme>) -> Self {
308        value.to_string()
309    }
310}
311
312impl PartialEq<[u8]> for Scheme<'_> {
313    fn eq(&self, other: &[u8]) -> bool {
314        self.as_str().as_bytes().eq_ignore_ascii_case(other)
315    }
316}
317
318impl<'scheme> PartialEq<Scheme<'scheme>> for [u8] {
319    fn eq(&self, other: &Scheme<'scheme>) -> bool {
320        other == self
321    }
322}
323
324impl<'a> PartialEq<&'a [u8]> for Scheme<'_> {
325    fn eq(&self, other: &&'a [u8]) -> bool {
326        self == *other
327    }
328}
329
330impl<'a, 'scheme> PartialEq<Scheme<'scheme>> for &'a [u8] {
331    fn eq(&self, other: &Scheme<'scheme>) -> bool {
332        other == *self
333    }
334}
335
336impl PartialEq<str> for Scheme<'_> {
337    fn eq(&self, other: &str) -> bool {
338        self == other.as_bytes()
339    }
340}
341
342impl<'scheme> PartialEq<Scheme<'scheme>> for str {
343    fn eq(&self, other: &Scheme<'scheme>) -> bool {
344        other == self.as_bytes()
345    }
346}
347
348impl<'a> PartialEq<&'a str> for Scheme<'_> {
349    fn eq(&self, other: &&'a str) -> bool {
350        self == other.as_bytes()
351    }
352}
353
354impl<'a, 'scheme> PartialEq<Scheme<'scheme>> for &'a str {
355    fn eq(&self, other: &Scheme<'scheme>) -> bool {
356        other == self.as_bytes()
357    }
358}
359
360impl<'scheme> TryFrom<&'scheme [u8]> for Scheme<'scheme> {
361    type Error = SchemeError;
362
363    fn try_from(value: &'scheme [u8]) -> Result<Scheme<'scheme>, Self::Error> {
364        let (scheme, rest) = parse_scheme(value)?;
365
366        if rest.is_empty() {
367            Ok(scheme)
368        } else {
369            Err(SchemeError::InvalidCharacter)
370        }
371    }
372}
373
374impl<'scheme> TryFrom<&'scheme str> for Scheme<'scheme> {
375    type Error = SchemeError;
376
377    fn try_from(value: &'scheme str) -> Result<Scheme<'scheme>, Self::Error> {
378        Scheme::try_from(value.as_bytes())
379    }
380}
381
382/// A scheme that is not in the
383/// [registered schemes](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml).
384///
385/// This is case-insensitive, and this is reflected in the equality and hash functions.
386#[derive(Clone, Debug)]
387#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
388pub struct UnregisteredScheme<'scheme> {
389    /// Whether the fragment is normalized.
390    normalized: bool,
391
392    /// The internal scheme source that is either owned or borrowed.
393    scheme: Cow<'scheme, str>,
394}
395
396impl UnregisteredScheme<'_> {
397    /// Returns a new unregistered scheme which is identical but has a lifetime tied to this
398    /// unregistered scheme.
399    pub fn as_borrowed(&self) -> UnregisteredScheme {
400        use self::Cow::*;
401
402        let scheme = match &self.scheme {
403            Borrowed(borrowed) => *borrowed,
404            Owned(owned) => owned.as_str(),
405        };
406
407        UnregisteredScheme {
408            normalized: self.normalized,
409            scheme: Cow::Borrowed(scheme),
410        }
411    }
412
413    /// Returns a `str` representation of the scheme.
414    ///
415    /// The case-sensitivity of the original string is preserved.
416    ///
417    /// # Examples
418    ///
419    /// ```
420    /// use std::convert::TryFrom;
421    ///
422    /// use uriparse::UnregisteredScheme;
423    ///
424    /// let scheme = UnregisteredScheme::try_from("TEST-scheme").unwrap();
425    /// assert_eq!(scheme.as_str(), "TEST-scheme");
426    /// ```
427    pub fn as_str(&self) -> &str {
428        &self.scheme
429    }
430
431    /// Converts the [`UnregisteredScheme`] into an owned copy.
432    ///
433    /// If you construct the scheme from a source with a non-static lifetime, you may run into
434    /// lifetime problems due to the way the struct is designed. Calling this function will ensure
435    /// that the returned value has a static lifetime.
436    ///
437    /// This is different from just cloning. Cloning the scheme will just copy the references, and
438    /// thus the lifetime will remain the same.
439    pub fn into_owned(self) -> UnregisteredScheme<'static> {
440        UnregisteredScheme {
441            normalized: self.normalized,
442            scheme: Cow::from(self.scheme.into_owned()),
443        }
444    }
445
446    /// Returns whether the scheme is normalized.
447    ///
448    /// A normalized scheme will be all lowercase.
449    ///
450    /// This function runs in constant-time.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use std::convert::TryFrom;
456    ///
457    /// use uriparse::UnregisteredScheme;
458    ///
459    /// let scheme = UnregisteredScheme::try_from("myscheme").unwrap();
460    /// assert!(scheme.is_normalized());
461    ///
462    /// let mut scheme = UnregisteredScheme::try_from("MyScHeMe").unwrap();
463    /// assert!(!scheme.is_normalized());
464    /// scheme.normalize();
465    /// assert!(scheme.is_normalized());
466    /// ```
467    pub fn is_normalized(&self) -> bool {
468        self.normalized
469    }
470
471    /// Normalizes the scheme so that it is all lowercase.
472    ///
473    /// # Examples
474    ///
475    /// ```
476    /// use std::convert::TryFrom;
477    ///
478    /// use uriparse::UnregisteredScheme;
479    ///
480    /// let mut scheme = UnregisteredScheme::try_from("myscheme").unwrap();
481    /// scheme.normalize();
482    /// assert_eq!(scheme, "myscheme");
483    ///
484    /// let mut scheme = UnregisteredScheme::try_from("MyScHeMe").unwrap();
485    /// scheme.normalize();
486    /// assert_eq!(scheme, "myscheme");
487    /// ```
488    pub fn normalize(&mut self) {
489        if !self.normalized {
490            // Unsafe: Schemes must be valid ASCII-US, so this is safe.
491            unsafe { normalize_string(&mut self.scheme.to_mut(), true) };
492            self.normalized = true;
493        }
494    }
495}
496
497impl AsRef<[u8]> for UnregisteredScheme<'_> {
498    fn as_ref(&self) -> &[u8] {
499        self.scheme.as_bytes()
500    }
501}
502
503impl AsRef<str> for UnregisteredScheme<'_> {
504    fn as_ref(&self) -> &str {
505        &self.scheme
506    }
507}
508
509impl Display for UnregisteredScheme<'_> {
510    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
511        formatter.write_str(&self.scheme)
512    }
513}
514
515impl Eq for UnregisteredScheme<'_> {}
516
517impl<'scheme> From<UnregisteredScheme<'scheme>> for String {
518    fn from(value: UnregisteredScheme<'scheme>) -> Self {
519        value.to_string()
520    }
521}
522
523impl Hash for UnregisteredScheme<'_> {
524    fn hash<H>(&self, state: &mut H)
525    where
526        H: Hasher,
527    {
528        self.scheme.to_lowercase().hash(state)
529    }
530}
531
532impl PartialEq for UnregisteredScheme<'_> {
533    fn eq(&self, other: &UnregisteredScheme) -> bool {
534        *self == *other.scheme.as_bytes()
535    }
536}
537
538impl PartialEq<[u8]> for UnregisteredScheme<'_> {
539    fn eq(&self, other: &[u8]) -> bool {
540        self.scheme.as_bytes().eq_ignore_ascii_case(&other)
541    }
542}
543
544impl<'scheme> PartialEq<UnregisteredScheme<'scheme>> for [u8] {
545    fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool {
546        other == self
547    }
548}
549
550impl<'a> PartialEq<&'a [u8]> for UnregisteredScheme<'_> {
551    fn eq(&self, other: &&'a [u8]) -> bool {
552        self == *other
553    }
554}
555
556impl<'a, 'scheme> PartialEq<UnregisteredScheme<'scheme>> for &'a [u8] {
557    fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool {
558        other == *self
559    }
560}
561
562impl PartialEq<str> for UnregisteredScheme<'_> {
563    fn eq(&self, other: &str) -> bool {
564        self == other.as_bytes()
565    }
566}
567
568impl<'scheme> PartialEq<UnregisteredScheme<'scheme>> for str {
569    fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool {
570        other == self.as_bytes()
571    }
572}
573
574impl<'a> PartialEq<&'a str> for UnregisteredScheme<'_> {
575    fn eq(&self, other: &&'a str) -> bool {
576        self == other.as_bytes()
577    }
578}
579
580impl<'a, 'scheme> PartialEq<UnregisteredScheme<'scheme>> for &'a str {
581    fn eq(&self, other: &UnregisteredScheme<'scheme>) -> bool {
582        other == self.as_bytes()
583    }
584}
585
586impl<'scheme> TryFrom<&'scheme [u8]> for UnregisteredScheme<'scheme> {
587    type Error = UnregisteredSchemeError;
588
589    fn try_from(value: &'scheme [u8]) -> Result<Self, Self::Error> {
590        match Scheme::try_from(value) {
591            Ok(Scheme::Unregistered(scheme)) => Ok(scheme),
592            _ => Err(UnregisteredSchemeError),
593        }
594    }
595}
596
597impl<'scheme> TryFrom<&'scheme str> for UnregisteredScheme<'scheme> {
598    type Error = UnregisteredSchemeError;
599
600    fn try_from(value: &'scheme str) -> Result<Self, Self::Error> {
601        UnregisteredScheme::try_from(value.as_bytes())
602    }
603}
604
605/// An error representing an invalid scheme.
606#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
607#[non_exhaustive]
608pub enum SchemeError {
609    /// The scheme component was empty.
610    Empty,
611
612    /// The scheme contained an invalid scheme character.
613    InvalidCharacter,
614
615    /// The scheme did not start with an alphabetic character.
616    StartsWithNonAlphabetic,
617}
618
619impl Display for SchemeError {
620    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
621        use self::SchemeError::*;
622
623        match self {
624            Empty => write!(formatter, "scheme is empty"),
625            InvalidCharacter => write!(formatter, "invalid scheme character"),
626            StartsWithNonAlphabetic => {
627                write!(formatter, "scheme starts with non-alphabetic character")
628            }
629        }
630    }
631}
632
633impl Error for SchemeError {}
634
635impl From<Infallible> for SchemeError {
636    fn from(_: Infallible) -> Self {
637        SchemeError::InvalidCharacter
638    }
639}
640
641/// An error representing that the unregistered scheme was an invalid scheme, or it was actually
642/// a registered scheme.
643#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
644pub struct UnregisteredSchemeError;
645
646impl Display for UnregisteredSchemeError {
647    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
648        write!(formatter, "invalid unregistered scheme")
649    }
650}
651
652impl Error for UnregisteredSchemeError {}
653
654/// The registration status of a scheme. See [RFC 7595](https://tools.ietf.org/html/rfc7595) for
655/// more information.
656#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
657pub enum SchemeStatus {
658    /// A scheme registered due to historical use. Generally, it is no longer in common use or is
659    /// not recommended.
660    Historical,
661
662    /// A scheme that has been expertly reviewed.
663    Permanent,
664
665    /// A scheme that was registered on a first come first served basis.
666    Provisional,
667
668    /// A scheme that is not currently registerd under
669    /// [iana.org](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml).
670    Unregistered,
671}
672
673impl SchemeStatus {
674    /// Returns whether the scheme status is historical.
675    ///
676    /// # Examples
677    ///
678    /// ```
679    /// use uriparse::Scheme;
680    ///
681    /// assert_eq!(Scheme::Fax.status().is_historical(), true);
682    /// assert_eq!(Scheme::HTTP.status().is_historical(), false);
683    /// ```
684    pub fn is_historical(self) -> bool {
685        match self {
686            SchemeStatus::Historical => true,
687            _ => false,
688        }
689    }
690
691    /// Returns whether the scheme status is historical.
692    ///
693    /// # Examples
694    ///
695    /// ```
696    /// use uriparse::Scheme;
697    ///
698    /// assert_eq!(Scheme::HTTP.status().is_permanent(), true);
699    /// assert_eq!(Scheme::IRC.status().is_permanent(), false);
700    /// ```
701    pub fn is_permanent(self) -> bool {
702        match self {
703            SchemeStatus::Permanent => true,
704            _ => false,
705        }
706    }
707
708    /// Returns whether the scheme status is historical.
709    ///
710    /// # Examples
711    ///
712    /// ```
713    /// use uriparse::Scheme;
714    ///
715    /// assert_eq!(Scheme::Git.status().is_provisional(), true);
716    /// assert_eq!(Scheme::RTSP.status().is_provisional(), false);
717    /// ```
718    pub fn is_provisional(self) -> bool {
719        match self {
720            SchemeStatus::Provisional => true,
721            _ => false,
722        }
723    }
724
725    /// Returns whether the scheme status is historical.
726    ///
727    /// # Examples
728    ///
729    /// ```
730    /// use std::convert::TryFrom;
731    ///
732    /// use uriparse::Scheme;
733    ///
734    /// let scheme = Scheme::try_from("test-scheme").unwrap();
735    /// assert_eq!(scheme.status().is_unregistered(), true);
736    /// assert_eq!(Scheme::HTTPS.status().is_unregistered(), false);
737    /// ```
738    pub fn is_unregistered(self) -> bool {
739        match self {
740            SchemeStatus::Unregistered => true,
741            _ => false,
742        }
743    }
744}
745
746schemes! {
747    (AAA, "aaa", SchemeStatus::Permanent);
748    (AAAS, "aaas", SchemeStatus::Permanent);
749    (About, "about", SchemeStatus::Permanent);
750    (ACAP, "acap", SchemeStatus::Permanent);
751    (ACCT, "acat", SchemeStatus::Permanent);
752    (ACR, "acr", SchemeStatus::Provisional);
753    (AdiumXtra, "adiumxtra", SchemeStatus::Provisional);
754    (AFP, "afp", SchemeStatus::Provisional);
755    (AFS, "afs", SchemeStatus::Provisional);
756    (AIM, "aim", SchemeStatus::Provisional);
757    (AMSS, "amss", SchemeStatus::Provisional);
758    (Android, "android", SchemeStatus::Provisional);
759    (AppData, "appdata", SchemeStatus::Provisional);
760    (APT, "apt", SchemeStatus::Provisional);
761    (Attachment, "attachment", SchemeStatus::Provisional);
762    (AW, "aw", SchemeStatus::Provisional);
763    (Barion, "barion", SchemeStatus::Provisional);
764    (BeShare, "beshare", SchemeStatus::Provisional);
765    (Bitcoin, "bitcoin", SchemeStatus::Provisional);
766    (BitcoinCash, "bitcoincash", SchemeStatus::Provisional);
767    (Blob, "blob", SchemeStatus::Provisional);
768    (Bolo, "bolo", SchemeStatus::Provisional);
769    (BrowserExt, "browserext", SchemeStatus::Provisional);
770    (Calculator, "calculator", SchemeStatus::Provisional);
771    (CallTo, "callto", SchemeStatus::Provisional);
772    (CAP, "cap", SchemeStatus::Permanent);
773    (Cast, "cast", SchemeStatus::Provisional);
774    (Casts, "casts", SchemeStatus::Provisional);
775    (Chrome, "chrome", SchemeStatus::Provisional);
776    (ChromeExtension, "chrome-extension", SchemeStatus::Provisional);
777    (CID, "cid", SchemeStatus::Permanent);
778    (CoAP, "coap", SchemeStatus::Permanent);
779    (CoAPTCP, "coap+tcp", SchemeStatus::Permanent);
780    (CoAPWS, "coap+ws", SchemeStatus::Permanent);
781    (CoAPS, "coaps", SchemeStatus::Permanent);
782    (CoAPSTCP, "coaps+tcp", SchemeStatus::Permanent);
783    (CoAPSWS, "coaps+ws", SchemeStatus::Permanent);
784    (ComEventBriteAttendee, "com-eventbrite-attendee", SchemeStatus::Provisional);
785    (Content, "content", SchemeStatus::Provisional);
786    (Conti, "conti", SchemeStatus::Provisional);
787    (CRID, "crid", SchemeStatus::Permanent);
788    (CVS, "cvs", SchemeStatus::Provisional);
789    (DAB, "dab", SchemeStatus::Provisional);
790    (Data, "data", SchemeStatus::Permanent);
791    (DAV, "dav", SchemeStatus::Permanent);
792    (Diaspora, "diaspora", SchemeStatus::Provisional);
793    (DICT, "dict", SchemeStatus::Permanent);
794    (DID, "did", SchemeStatus::Provisional);
795    (DIS, "dis", SchemeStatus::Provisional);
796    (DLNAPlayContainer, "dlna-playcontainer", SchemeStatus::Provisional);
797    (DLNAPlaySingle, "dlna-playsingle", SchemeStatus::Provisional);
798    (DNS, "dns", SchemeStatus::Permanent);
799    (DNTP, "dntp", SchemeStatus::Provisional);
800    (DPP, "dpp", SchemeStatus::Provisional);
801    (DRM, "drm", SchemeStatus::Provisional);
802    (Drop, "drop", SchemeStatus::Provisional);
803    (DTN, "dtn", SchemeStatus::Provisional);
804    (DVB, "dvb", SchemeStatus::Provisional);
805    (ED2K, "ed2k", SchemeStatus::Provisional);
806    (ELSI, "elsi", SchemeStatus::Provisional);
807    (Example, "example", SchemeStatus::Permanent);
808    (FaceTime, "facetime", SchemeStatus::Provisional);
809    (Fax, "fax", SchemeStatus::Historical);
810    (Feed, "feed", SchemeStatus::Provisional);
811    (FeedReady, "feedready", SchemeStatus::Provisional);
812    (File, "file", SchemeStatus::Permanent);
813    (FileSystem, "filesystem", SchemeStatus::Historical);
814    (Finger, "finger", SchemeStatus::Provisional);
815    (Fish, "fish", SchemeStatus::Provisional);
816    (FM, "fm", SchemeStatus::Provisional);
817    (FTP, "ftp", SchemeStatus::Permanent);
818    (FuchsiaPkg, "fuchsia-pkg", SchemeStatus::Provisional);
819    (Geo, "geo", SchemeStatus::Permanent);
820    (GG, "gg", SchemeStatus::Provisional);
821    (Git, "git", SchemeStatus::Provisional);
822    (GizmoProject, "gizmoproject", SchemeStatus::Provisional);
823    (Go, "go", SchemeStatus::Permanent);
824    (Gopher, "gopher", SchemeStatus::Permanent);
825    (Graph, "graph", SchemeStatus::Provisional);
826    (GTalk, "gtalk", SchemeStatus::Provisional);
827    (H323, "h323", SchemeStatus::Permanent);
828    (HAM, "ham", SchemeStatus::Provisional);
829    (HCAP, "hcap", SchemeStatus::Provisional);
830    (HCP, "hcp", SchemeStatus::Provisional);
831    (HTTP, "http", SchemeStatus::Permanent);
832    (HTTPS, "https", SchemeStatus::Permanent);
833    (HXXP, "hxxp", SchemeStatus::Provisional);
834    (HXXPS, "hxxps", SchemeStatus::Provisional);
835    (HydraZone, "hydrazone", SchemeStatus::Provisional);
836    (IAX, "iax", SchemeStatus::Permanent);
837    (ICAP, "icap", SchemeStatus::Permanent);
838    (Icon, "icon", SchemeStatus::Provisional);
839    (IM, "im", SchemeStatus::Permanent);
840    (IMAP, "imap", SchemeStatus::Permanent);
841    (Info, "info", SchemeStatus::Permanent);
842    (IoTDisc, "iotdisc", SchemeStatus::Provisional);
843    (IPN, "ipn", SchemeStatus::Provisional);
844    (IPP, "ipp", SchemeStatus::Permanent);
845    (IPPS, "ipps", SchemeStatus::Permanent);
846    (IRC, "irc", SchemeStatus::Provisional);
847    (IRC6, "irc6", SchemeStatus::Provisional);
848    (IRCS, "ircs", SchemeStatus::Provisional);
849    (IRIS, "iris", SchemeStatus::Permanent);
850    (IRISBEEP, "iris.beep", SchemeStatus::Permanent);
851    (IRISLWZ, "iris.lwz", SchemeStatus::Permanent);
852    (IRISXPC, "iris.xpc", SchemeStatus::Permanent);
853    (IRISXPCS, "iris.xpcs", SchemeStatus::Permanent);
854    (IsoStore, "isostore", SchemeStatus::Provisional);
855    (ITMS, "itms", SchemeStatus::Provisional);
856    (Jabber, "jabber", SchemeStatus::Permanent);
857    (JAR, "jar", SchemeStatus::Provisional);
858    (JMS, "jms", SchemeStatus::Provisional);
859    (KeyParc, "keyparc", SchemeStatus::Provisional);
860    (LastFM, "lastfm", SchemeStatus::Provisional);
861    (LDAP, "ldap", SchemeStatus::Permanent);
862    (LDAPS, "ldaps", SchemeStatus::Provisional);
863    (LoRaWAN, "lorawan", SchemeStatus::Provisional);
864    (LVLT, "lvlt", SchemeStatus::Provisional);
865    (Magnet, "magnet", SchemeStatus::Provisional);
866    (MailServer, "mailserver", SchemeStatus::Historical);
867    (MailTo, "mailto", SchemeStatus::Permanent);
868    (Maps, "maps", SchemeStatus::Provisional);
869    (Market, "market", SchemeStatus::Provisional);
870    (Message, "message", SchemeStatus::Provisional);
871    (MicrosoftWindowsCamera, "microsoft.windows.camera", SchemeStatus::Provisional);
872    (MicrosoftWindowsCameraMultiPicker, "microsoft.windows.camera.multipicker", SchemeStatus::Provisional);
873    (MicrosoftWindowsCameraPicker, "microsoft.windows.camera.picker", SchemeStatus::Provisional);
874    (MID, "mid", SchemeStatus::Permanent);
875    (MMS, "mms", SchemeStatus::Provisional);
876    (Modem, "modem", SchemeStatus::Historical);
877    (MongoDB, "mongodb", SchemeStatus::Provisional);
878    (Moz, "moz", SchemeStatus::Provisional);
879    (MSAccess, "ms-access", SchemeStatus::Provisional);
880    (MSBrowserExtension, "ms-browser-extension", SchemeStatus::Provisional);
881    (MSCalculator, "ms-calculator", SchemeStatus::Provisional);
882    (MSDriveTo, "ms-drive-to", SchemeStatus::Provisional);
883    (MSEnrollment, "ms-enrollment", SchemeStatus::Provisional);
884    (MSExcel, "ms-excel", SchemeStatus::Provisional);
885    (MSEyeControlSpeech, "ms-eyecontrolspeech", SchemeStatus::Provisional);
886    (MSGameBarServices, "ms-gamebaresrvices", SchemeStatus::Provisional);
887    (MSGamingOverlay, "ms-gamingoverlay", SchemeStatus::Provisional);
888    (MSGetOffice, "ms-getoffice", SchemeStatus::Provisional);
889    (MSHelp, "ms-help", SchemeStatus::Provisional);
890    (MSInfoPath, "ms-infopath", SchemeStatus::Provisional);
891    (MSInputApp, "ms-inputapp", SchemeStatus::Provisional);
892    (MSLockScreenComponentConfig, "ms-lockscreencomponent-config", SchemeStatus::Provisional);
893    (MSMediaStreamID, "ms-media-stream-id", SchemeStatus::Provisional);
894    (MSMixedRealityCapture, "ms-mixedrealitycapture", SchemeStatus::Provisional);
895    (MSOfficeApp, "ms-officeapp", SchemeStatus::Provisional);
896    (MSPeople, "ms-people", SchemeStatus::Provisional);
897    (MSProject, "ms-project", SchemeStatus::Provisional);
898    (MSPowerPoint, "ms-powerpoint", SchemeStatus::Provisional);
899    (MSPublisher, "ms-publisher", SchemeStatus::Provisional);
900    (MSRestoreTabCompanion, "ms-restoretabcompanion", SchemeStatus::Provisional);
901    (MSS, "mss", SchemeStatus::Provisional);
902    (MSScreenClip, "ms-screenclip", SchemeStatus::Provisional);
903    (MSScreenSketch, "ms-screensketch", SchemeStatus::Provisional);
904    (MSSearch, "ms-search", SchemeStatus::Provisional);
905    (MSSearchRepair, "ms-search-repair", SchemeStatus::Provisional);
906    (MSSecondaryScreenController, "ms-secondary-screen-controller", SchemeStatus::Provisional);
907    (MSSeocndaryScreenSetup, "ms-secondary-screen-setup", SchemeStatus::Provisional);
908    (MSSettings, "ms-settings", SchemeStatus::Provisional);
909    (MSSettingsAirplaneMode, "ms-settings-airplanemode", SchemeStatus::Provisional);
910    (MSSettingsBluetooth, "ms-settings-bluetooth", SchemeStatus::Provisional);
911    (MSSettingsCamera, "ms-settings-camera", SchemeStatus::Provisional);
912    (MSSettingsCellular, "ms-settings-cellular", SchemeStatus::Provisional);
913    (MSSettingsCloudStorage, "ms-settings-cloudstorage", SchemeStatus::Provisional);
914    (MSSettingsConnectableDevices, "ms-settings-connectabledevices", SchemeStatus::Provisional);
915    (MSSettingsDisplaysTopology, "ms-settings-displays-topology", SchemeStatus::Provisional);
916    (MSSettingsEmailAndAccounts, "ms-settings-emailandaccounts", SchemeStatus::Provisional);
917    (MSSettingsLanguage, "ms-settings-language", SchemeStatus::Provisional);
918    (MSSettingsLocation, "ms-settings-location", SchemeStatus::Provisional);
919    (MSSettingsLock, "ms-settings-lock", SchemeStatus::Provisional);
920    (MSSettingsNFCTransactions, "ms-settings-nfctransactions", SchemeStatus::Provisional);
921    (MSSettingsNotifications, "ms-settings-notifications", SchemeStatus::Provisional);
922    (MSSettingsPower, "ms-settings-power", SchemeStatus::Provisional);
923    (MSSettingsPrivacy, "ms-settings-privacy", SchemeStatus::Provisional);
924    (MSSettingsProximity, "ms-settings-proximity", SchemeStatus::Provisional);
925    (MSSettingsScreenRotation, "ms-settings-screenrotation", SchemeStatus::Provisional);
926    (MSSettingsWiFi, "ms-settings-wifi", SchemeStatus::Provisional);
927    (MSSettingsWorkplace, "ms-settings-workplace", SchemeStatus::Provisional);
928    (MSSPD, "ms-spd", SchemeStatus::Provisional);
929    (MSSTTOverlay, "ms-sttoverlay", SchemeStatus::Provisional);
930    (MSTransitTo, "ms-transit-to", SchemeStatus::Provisional);
931    (MSUserActivitySet, "ms-useractivityset", SchemeStatus::Provisional);
932    (MSVirtualTouchPad, "ms-virtualtouchpad", SchemeStatus::Provisional);
933    (MSVisio, "ms-visio", SchemeStatus::Provisional);
934    (MSWalkTo, "ms-walk-to", SchemeStatus::Provisional);
935    (MSWhiteboard, "ms-whiteboard", SchemeStatus::Provisional);
936    (MSWhiteboardCMD, "ms-whiteboard-cmd", SchemeStatus::Provisional);
937    (MSWord, "ms-word", SchemeStatus::Provisional);
938    (MSNIM, "msnim", SchemeStatus::Provisional);
939    (MSRP, "msrp", SchemeStatus::Permanent);
940    (MSRPS, "msrps", SchemeStatus::Permanent);
941    (MTQP, "mtqp", SchemeStatus::Permanent);
942    (Mumble, "mumble", SchemeStatus::Provisional);
943    (MUpdate, "mupdate", SchemeStatus::Permanent);
944    (MVN, "mvn", SchemeStatus::Provisional);
945    (News, "news", SchemeStatus::Permanent);
946    (NFS, "nfs", SchemeStatus::Permanent);
947    (NI, "ni", SchemeStatus::Permanent);
948    (NIH, "nih", SchemeStatus::Permanent);
949    (NNTP, "nntp", SchemeStatus::Permanent);
950    (Notes, "notes", SchemeStatus::Provisional);
951    (OCF, "ocf", SchemeStatus::Provisional);
952    (OID, "oid", SchemeStatus::Provisional);
953    (OneNote, "onenote", SchemeStatus::Provisional);
954    (OneNoteCMD, "onenote-cmd", SchemeStatus::Provisional);
955    (OpaqueLockToken, "opaquelocktoken", SchemeStatus::Permanent);
956    (OpenPGP4FPR, "openpgp4fpr", SchemeStatus::Provisional);
957    (Pack, "pack", SchemeStatus::Historical);
958    (Palm, "palm", SchemeStatus::Provisional);
959    (Paparazzi, "paparazzi", SchemeStatus::Provisional);
960    (PKCS11, "pkcs11", SchemeStatus::Permanent);
961    (Platform, "platform", SchemeStatus::Provisional);
962    (POP, "pop", SchemeStatus::Permanent);
963    (Pres, "pres", SchemeStatus::Permanent);
964    (Prospero, "prospero", SchemeStatus::Historical);
965    (Proxy, "proxy", SchemeStatus::Provisional);
966    (PWID, "pwid", SchemeStatus::Provisional);
967    (PSYC, "psyc", SchemeStatus::Provisional);
968    (QB, "qb", SchemeStatus::Provisional);
969    (Query, "query", SchemeStatus::Provisional);
970    (Redis, "redis", SchemeStatus::Provisional);
971    (RedisS, "rediss", SchemeStatus::Provisional);
972    (Reload, "reload", SchemeStatus::Permanent);
973    (Res, "res", SchemeStatus::Provisional);
974    (Resource, "resource", SchemeStatus::Provisional);
975    (RMI, "rmi", SchemeStatus::Provisional);
976    (RSync, "rsync", SchemeStatus::Provisional);
977    (RTMFP, "rtmfp", SchemeStatus::Provisional);
978    (RTMP, "rtmp", SchemeStatus::Provisional);
979    (RTSP, "rtsp", SchemeStatus::Permanent);
980    (RTSPS, "rtsps", SchemeStatus::Permanent);
981    (RTSPU, "rtspu", SchemeStatus::Permanent);
982    (SecondLife, "secondlife", SchemeStatus::Provisional);
983    (Service, "service", SchemeStatus::Permanent);
984    (Session, "session", SchemeStatus::Permanent);
985    (SFTP, "sftp", SchemeStatus::Provisional);
986    (SGN, "sgn", SchemeStatus::Provisional);
987    (SHTTP, "shttp", SchemeStatus::Permanent);
988    (Sieve, "sieve", SchemeStatus::Permanent);
989    (SIP, "sip", SchemeStatus::Permanent);
990    (SIPS, "sips", SchemeStatus::Permanent);
991    (SimpleLedger, "simpleledger", SchemeStatus::Provisional);
992    (Skype, "skype", SchemeStatus::Provisional);
993    (SMB, "smb", SchemeStatus::Provisional);
994    (SMS, "sms", SchemeStatus::Permanent);
995    (SMTP, "smtp", SchemeStatus::Provisional);
996    (SNews, "snews", SchemeStatus::Historical);
997    (SNMP, "snmp", SchemeStatus::Permanent);
998    (SOAPBEEP, "soap.beep", SchemeStatus::Permanent);
999    (SOAPBEEPS, "soap.beeps", SchemeStatus::Permanent);
1000    (Soldat, "soldat", SchemeStatus::Provisional);
1001    (SPIFFE, "spiffe", SchemeStatus::Provisional);
1002    (Spotify, "spotify", SchemeStatus::Provisional);
1003    (SSH, "ssh", SchemeStatus::Provisional);
1004    (Steam, "steam", SchemeStatus::Provisional);
1005    (STUN, "stun", SchemeStatus::Permanent);
1006    (STUNS, "stuns", SchemeStatus::Permanent);
1007    (Submit, "submit", SchemeStatus::Provisional);
1008    (SVN, "svn", SchemeStatus::Provisional);
1009    (Tag, "tag", SchemeStatus::Permanent);
1010    (TeamSpeak, "teamspeak", SchemeStatus::Provisional);
1011    (Tel, "tel", SchemeStatus::Permanent);
1012    (TeliaEID, "teliaeid", SchemeStatus::Provisional);
1013    (Telnet, "telnet", SchemeStatus::Permanent);
1014    (TFTP, "tftp", SchemeStatus::Permanent);
1015    (Things, "things", SchemeStatus::Provisional);
1016    (ThisMessage, "thismessage", SchemeStatus::Permanent);
1017    (TIP, "tip", SchemeStatus::Permanent);
1018    (TN3270, "tn3270", SchemeStatus::Permanent);
1019    (Tool, "tool", SchemeStatus::Provisional);
1020    (TURN, "turn", SchemeStatus::Permanent);
1021    (TURNS, "turns", SchemeStatus::Permanent);
1022    (TV, "tv", SchemeStatus::Permanent);
1023    (UDP, "udp", SchemeStatus::Provisional);
1024    (Unreal, "unreal", SchemeStatus::Provisional);
1025    (URN, "urn", SchemeStatus::Permanent);
1026    (UT2004, "ut2004", SchemeStatus::Provisional);
1027    (VEvent, "v-event", SchemeStatus::Provisional);
1028    (VEMMI, "vemmi", SchemeStatus::Permanent);
1029    (Ventrilo, "ventrilo", SchemeStatus::Provisional);
1030    (Videotex, "videotex", SchemeStatus::Historical);
1031    (VNC, "vnc", SchemeStatus::Permanent);
1032    (ViewSource, "view-source", SchemeStatus::Provisional);
1033    (WAIS, "wais", SchemeStatus::Historical);
1034    (Webcal, "webcal", SchemeStatus::Provisional);
1035    (WPID, "wpid", SchemeStatus::Historical);
1036    (WS, "ws", SchemeStatus::Permanent);
1037    (WSS, "wss", SchemeStatus::Permanent);
1038    (WTAI, "wtai", SchemeStatus::Provisional);
1039    (WYCIWYG, "wyciwyg", SchemeStatus::Provisional);
1040    (XCON, "xcon", SchemeStatus::Permanent);
1041    (XCONUserID, "xcon-userid", SchemeStatus::Permanent);
1042    (Xfire, "xfire", SchemeStatus::Provisional);
1043    (XMLRPCBEEP, "xmlrpc.beep", SchemeStatus::Permanent);
1044    (XMLRPCBEEPS, "xmlrpc.beeps", SchemeStatus::Permanent);
1045    (XMPP, "xmpp", SchemeStatus::Permanent);
1046    (XRI, "xri", SchemeStatus::Provisional);
1047    (YMSGR, "ymsgr", SchemeStatus::Provisional);
1048    (Z3950, "z39.50", SchemeStatus::Historical);
1049    (Z3950R, "z39.50r", SchemeStatus::Permanent);
1050    (Z3950S, "z39.50s", SchemeStatus::Permanent);
1051}
1052
1053#[cfg(test)]
1054mod test {
1055    use super::*;
1056
1057    #[test]
1058    fn test_scheme_normalize() {
1059        fn test_case(value: &str, expected: &str) {
1060            let mut scheme = Scheme::try_from(value).unwrap();
1061            scheme.normalize();
1062            assert_eq!(scheme, expected);
1063        }
1064
1065        test_case("http", "http");
1066        test_case("SCHEME", "scheme");
1067    }
1068
1069    #[test]
1070    fn test_scheme_parse() {
1071        use self::SchemeError::*;
1072
1073        assert_eq!(Scheme::try_from("scheme").unwrap(), "scheme");
1074        assert_eq!(Scheme::try_from("HTTP").unwrap(), "http");
1075        assert_eq!(Scheme::try_from("SCHEME").unwrap(), "SCHEME");
1076
1077        assert_eq!(Scheme::try_from(""), Err(Empty));
1078        assert_eq!(Scheme::try_from("a:"), Err(InvalidCharacter));
1079        assert_eq!(Scheme::try_from("1"), Err(StartsWithNonAlphabetic));
1080    }
1081}