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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Enable all clippy lints and enforce, and opt out of individual lints
#![cfg_attr(feature = "cargo-clippy", warn(clippy::cargo, clippy::pedantic, clippy::nursery))]
#![cfg_attr(
    feature = "cargo-clippy",
    allow(clippy::module_name_repetitions, clippy::must_use_candidate, clippy::non_ascii_literal)
)]
// Force certain lints to be errors
#![deny(unused_must_use)]
//
#![doc(html_root_url = "https://docs.rs/rfc2396/1.0.5")]

//! To use this crate, simply call one of the `validate*` functions, depending on your use case and desired return type:
//!
//! - [`validate(S) -> bool`]
//! - [`validate_nom(S) -> nom::IResult<&str, &str>`]
//! - [`validate_opt(S) -> Option<&str>`]
//! - [`validate_res(S) -> anyhow::Result<&str>`]
//!
//! Where `S` is some type that implements [`Into<&str>`]; for example, [`&str`] itself!
//!
//! All functions except the [`_nom`] variant will additionally ensure that the entire input is consumed and
//! matches exactly what was parsed internally. If you require that this check is not done (e.g. as part of a larger
//! parsing exercise), then use the [`_nom`] variant.
//!
//! [`validate(S) -> bool`]: fn.validate.html
//! [`validate_nom(S) -> nom::IResult<&str, &str>`]: fn.validate_nom.html
//! [`validate_opt(S) -> Option<&str>`]: fn.validate_opt.html
//! [`validate_res(S) -> anyhow::Result<&str>`]: fn.validate_res.html
//! [`Into<&str>`]: https://doc.rust-lang.org/std/convert/trait.Into.html
//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
//! [`_nom`]: fn.validate_nom.html
//
use anyhow::Context;

mod combinators;
mod parsers;

/// Validate the given URI against [RFC2396], returning a [`bool`] indicating its validity.
///
/// Entire input consumption is also checked as part of validation.
///
/// # Examples
///
/// ```rust
/// let is_valid = rfc2396::validate("https://example.com");
/// assert!(is_valid);
/// ```
///
/// [RFC2396]: https://tools.ietf.org/html/rfc2396
/// [`bool`]: https://doc.rust-lang.org/std/primitive.bool.html
pub fn validate<'a, S>(uri: S) -> bool
where
    S: Into<&'a str>,
{
    let input = uri.into();

    match crate::parsers::uri::uri_reference(input) {
        Ok((_, matched)) => input == matched,
        Err(_) => false,
    }
}

/// Validate the given URI against [RFC2396], returning a [`nom::IResult<&str, &str>`] indicating its validity.
///
/// # Examples
///
/// ```rust
/// let validation = rfc2396::validate_nom("https://example.com");
/// assert_eq!(validation, nom::IResult::Ok(("", "https://example.com")));
/// ```
///
/// # Errors
///
/// If validation fails, the raw, default [`nom::IResult`] generated by [`nom`] will be returned.
///
/// [RFC2396]: https://tools.ietf.org/html/rfc2396
/// [`nom::IResult<&str, &str>`]: https://docs.rs/nom/6.0.1/nom/type.IResult.html
/// [`nom::IResult`]: https://docs.rs/nom/6.0.1/nom/type.IResult.html
/// [`nom`]: https://crates.io/crates/nom
pub fn validate_nom<'a, S>(uri: S) -> nom::IResult<&'a str, &'a str>
where
    S: Into<&'a str>,
{
    crate::parsers::uri::uri_reference(uri.into())
}

/// Validate the given URI against [RFC2396], returning an [`Option<&str>`] indicating its validity.
///
/// Entire input consumption is also checked as part of validation. The embedded [`&str`] is the input.
///
/// # Examples
///
/// ```rust
/// let valid_uri = rfc2396::validate_opt("https://example.com");
/// assert_eq!(valid_uri, Some("https://example.com"));
/// ```
///
/// [RFC2396]: https://tools.ietf.org/html/rfc2396
/// [`Option<&str>`]: https://doc.rust-lang.org/std/option/enum.Option.html
/// [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
pub fn validate_opt<'a, S>(uri: S) -> Option<&'a str>
where
    S: Into<&'a str>,
{
    let input = uri.into();

    match crate::parsers::uri::uri_reference(input) {
        Ok((_, matched)) if matched == input => Some(input),
        _ => None,
    }
}

/// Validate the given URI against [RFC2396], returning an [`anyhow::Result<&str>`] indicating its validity.
///
/// Entire input consumption is also checked as part of validation. The embedded [`&str`] is the input.
///
/// # Examples
///
/// ```rust
/// # /// <https://stackoverflow.com/a/58119924>
/// # fn type_of<T>(_: &T) -> &str {
/// #     std::any::type_name::<T>()
/// # }
/// #
/// # fn main() -> anyhow::Result<()> {
/// let validation = rfc2396::validate_res("https://example.com");
/// assert_eq!(type_of(&validation), std::any::type_name::<anyhow::Result<&str>>());
/// assert_eq!(validation?, "https://example.com");
/// #
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// If validation fails, a default [`nom::IResult`] converted into an [`anyhow::Result`] with additional context added
/// by [`anyhow`] will be returned.
///
/// [RFC2396]: https://tools.ietf.org/html/rfc2396
/// [`anyhow::Result<&str>`]: https://docs.rs/anyhow/1.0.36/anyhow/type.Result.html
/// [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
/// [`nom::IResult`]: https://docs.rs/nom/6.0.1/nom/type.IResult.html
/// [`anyhow::Result`]: https://docs.rs/anyhow/1.0.36/anyhow/type.Result.html
/// [`anyhow`]:https://crates.io/crates/anyhow
pub fn validate_res<'a, S>(uri: S) -> anyhow::Result<&'a str>
where
    S: Into<&'a str>,
{
    let input = uri.into();

    (match crate::parsers::uri::uri_reference(input) {
        Ok((_, matched)) => {
            if matched == input {
                Ok(input)
            } else {
                Err(anyhow::anyhow!("URI matched by parser (`{}`) does not equal input (`{}`)", matched, input))
            }
        }
        Err(error) => Err(anyhow::anyhow!(error.map(|mut e| e.input = input))),
    })
    .context("failed to parse URI")
}