1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
//! Fragment string.

use core::convert::TryFrom;

use crate::{
    spec::Spec,
    validate::{fragment, Error},
};

define_custom_string_slice! {
    /// A borrowed slice of an IRI fragment (i.e. after the first `#` character).
    ///
    /// This corresponds to [`ifragment` rule] in [RFC 3987] (and [`fragment` rule] in [RFC 3986]).
    /// The rule for `ifragment` is `*( ipchar / "/" / "?" )`.
    ///
    /// # Valid values
    ///
    /// This type can have an IRI fragment.
    /// Note that the IRI `foo://bar/baz#qux` has the fragment `qux`, **not** `#qux`.
    ///
    /// ```
    /// # use iri_string::types::IriFragmentStr;
    /// assert!(IriFragmentStr::new("").is_ok());
    /// assert!(IriFragmentStr::new("foo").is_ok());
    /// assert!(IriFragmentStr::new("foo/bar").is_ok());
    /// assert!(IriFragmentStr::new("/foo/bar").is_ok());
    /// assert!(IriFragmentStr::new("//foo/bar").is_ok());
    /// assert!(IriFragmentStr::new("https://user:pass@example.com:8080").is_ok());
    /// assert!(IriFragmentStr::new("https://example.com/").is_ok());
    /// ```
    ///
    /// Some characters and sequences cannot used in a fragment.
    ///
    /// ```
    /// # use iri_string::types::IriFragmentStr;
    /// // `<` and `>` cannot directly appear in an IRI reference.
    /// assert!(IriFragmentStr::new("<not allowed>").is_err());
    /// // Broken percent encoding cannot appear in an IRI reference.
    /// assert!(IriFragmentStr::new("%").is_err());
    /// assert!(IriFragmentStr::new("%GG").is_err());
    /// // Hash sign `#` cannot appear in an IRI fragment.
    /// assert!(IriFragmentStr::new("#hash").is_err());
    /// ```
    ///
    /// [RFC 3986]: https://tools.ietf.org/html/rfc3986
    /// [RFC 3987]: https://tools.ietf.org/html/rfc3987
    /// [`fragment` rule]: https://tools.ietf.org/html/rfc3986#section-3.5
    /// [`ifragment` rule]: https://tools.ietf.org/html/rfc3987#section-2.2
    struct RiFragmentStr {
        validator = fragment,
        expecting_msg = "IRI fragment string",
    }
}

#[cfg(feature = "alloc")]
define_custom_string_owned! {
    /// An owned string of an IRI fragment (i.e. after the first `#` character).
    ///
    /// This corresponds to [`ifragment` rule] in [RFC 3987] (and [`fragment` rule] in [RFC 3986]).
    /// The rule for `absolute-IRI` is `*( ipchar / "/" / "?" )`.
    ///
    /// For details, see the documentation for [`RiFragmentStr`].
    ///
    /// Enabled by `alloc` or `std` feature.
    ///
    /// [RFC 3986]: https://tools.ietf.org/html/rfc3986
    /// [RFC 3987]: https://tools.ietf.org/html/rfc3987
    /// [`fragment` rule]: https://tools.ietf.org/html/rfc3986#section-3.5
    /// [`ifragment` rule]: https://tools.ietf.org/html/rfc3987#section-2.2
    /// [`RiFragmentStr`]: struct.RiFragmentStr.html
    struct RiFragmentString {
        validator = fragment,
        slice = RiFragmentStr,
        expecting_msg = "IRI fragment string",
    }
}

impl<S: Spec> RiFragmentStr<S> {
    /// Creates a new `&RiFragmentStr` from the fragment part prefixed by `#`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use iri_string::types::IriFragmentStr;
    /// assert!(IriFragmentStr::from_prefixed("#").is_ok());
    /// assert!(IriFragmentStr::from_prefixed("#foo").is_ok());
    /// assert!(IriFragmentStr::from_prefixed("#foo/bar").is_ok());
    /// assert!(IriFragmentStr::from_prefixed("#/foo/bar").is_ok());
    /// assert!(IriFragmentStr::from_prefixed("#//foo/bar").is_ok());
    /// assert!(IriFragmentStr::from_prefixed("#https://user:pass@example.com:8080").is_ok());
    /// assert!(IriFragmentStr::from_prefixed("#https://example.com/").is_ok());
    ///
    /// // `<` and `>` cannot directly appear in an IRI.
    /// assert!(IriFragmentStr::from_prefixed("#<not allowed>").is_err());
    /// // Broken percent encoding cannot appear in an IRI.
    /// assert!(IriFragmentStr::new("#%").is_err());
    /// assert!(IriFragmentStr::new("#%GG").is_err());
    /// // `#` prefix is expected.
    /// assert!(IriFragmentStr::from_prefixed("").is_err());
    /// assert!(IriFragmentStr::from_prefixed("foo").is_err());
    /// // Hash sign `#` cannot appear in an IRI fragment.
    /// assert!(IriFragmentStr::from_prefixed("##hash").is_err());
    /// ```
    pub fn from_prefixed(s: &str) -> Result<&Self, Error> {
        if !s.starts_with('#') {
            return Err(Error::new());
        }
        TryFrom::try_from(&s[1..])
    }
}