web_url/
param.rs

1use crate::parse::Error;
2use crate::parse::Error::InvalidParam;
3use std::fmt::{Display, Formatter};
4
5/// A web-based URL query parameter.
6///
7/// # Validation
8/// Both the name and value of a query parameter may be the empty string. The value string may also
9/// be absent altogether which signifies a missing '=' in the query parameter string.
10///
11/// Query parameter names & values can contain any US-ASCII letters, numbers, or punctuation chars
12/// excluding '&' and '#' since these chars denote the end of the parameter or query in the URL.
13/// Names cannot contain the '=' char since this denotes the end of the query parameter name.
14#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
15pub struct Param<'a> {
16    name: &'a str,
17    value: Option<&'a str>,
18}
19
20impl<'a> Param<'a> {
21    //! Construction
22
23    /// Creates a new query parameter.
24    ///
25    /// # Safety
26    /// The `name` and `value` must be valid.
27    pub unsafe fn new(name: &'a str, value: Option<&'a str>) -> Self {
28        debug_assert!(Self::is_valid_name(name));
29        debug_assert!(value.iter().all(|v| Self::is_valid_value(v)));
30
31        Self { name, value }
32    }
33
34    /// Creates a new query parameter from the `param`.
35    ///
36    /// The `param` will be split on the first '=' char. If not present the value will be `None`.
37    ///
38    /// # Safety
39    /// The `param` must be valid.
40    pub unsafe fn from_str(param: &'a str) -> Self {
41        if let Some(eq) = param.as_bytes().iter().position(|c| *c == b'=') {
42            let (name, eq_value) = param.split_at(eq);
43            Self::new(name, Some(&eq_value[1..]))
44        } else {
45            Self::new(param, None)
46        }
47    }
48}
49
50impl<'a> TryFrom<&'a str> for Param<'a> {
51    type Error = Error;
52
53    fn try_from(param: &'a str) -> Result<Self, Self::Error> {
54        if let Some(eq) = param.as_bytes().iter().position(|c| *c == b'=') {
55            let (name, eq_value) = param.split_at(eq);
56            if Self::is_valid_name(name) && Self::is_valid_value(eq_value) {
57                Ok(unsafe { Self::new(name, Some(&eq_value[1..])) })
58            } else {
59                Err(InvalidParam)
60            }
61        } else if Self::is_valid_name(param) {
62            Ok(unsafe { Self::new(param, None) })
63        } else {
64            Err(InvalidParam)
65        }
66    }
67}
68
69impl<'a> Param<'a> {
70    //! Validation
71
72    /// Checks if the `name` is valid.
73    pub fn is_valid_name(name: &str) -> bool {
74        name.as_bytes().iter().all(|c| {
75            c.is_ascii_alphanumeric()
76                || (c.is_ascii_punctuation() && *c != b'&' && *c != b'#' && *c != b'=')
77        })
78    }
79
80    /// Checks if the `value` is valid.
81    pub fn is_valid_value(value: &str) -> bool {
82        value.as_bytes().iter().all(|c| {
83            c.is_ascii_alphanumeric() || (c.is_ascii_punctuation() && *c != b'&' && *c != b'#')
84        })
85    }
86}
87
88impl<'a> Param<'a> {
89    //! Properties
90
91    /// Gets the name.
92    pub const fn name(&self) -> &str {
93        self.name
94    }
95
96    /// Gets the optional value.
97    pub const fn value(&self) -> Option<&str> {
98        self.value
99    }
100}
101
102impl<'a> Display for Param<'a> {
103    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104        write!(f, "{}", self.name)?;
105        if let Some(value) = self.value {
106            write!(f, "={}", value)?;
107        }
108        Ok(())
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use crate::Param;
115
116    #[test]
117    fn new() {
118        let param: Param = unsafe { Param::new("name", Some("value")) };
119        assert_eq!(param.name, "name");
120        assert_eq!(param.value, Some("value"));
121    }
122
123    #[test]
124    fn from_str() {
125        let param: Param = unsafe { Param::from_str("name") };
126        assert_eq!(param.name, "name");
127        assert_eq!(param.value, None);
128
129        let param: Param = unsafe { Param::from_str("name=") };
130        assert_eq!(param.name, "name");
131        assert_eq!(param.value, Some(""));
132
133        let param: Param = unsafe { Param::from_str("name=value") };
134        assert_eq!(param.name, "name");
135        assert_eq!(param.value, Some("value"));
136    }
137
138    #[test]
139    fn properties() {
140        let param: Param = unsafe { Param::new("name", Some("value")) };
141        assert_eq!(param.name(), "name");
142        assert_eq!(param.value(), Some("value"));
143
144        let param: Param = unsafe { Param::new("name", None) };
145        assert_eq!(param.name(), "name");
146        assert_eq!(param.value(), None);
147    }
148
149    #[test]
150    fn display() {
151        let param: Param = unsafe { Param::new("name", Some("value")) };
152        assert_eq!(param.to_string(), "name=value");
153
154        let param: Param = unsafe { Param::new("name", None) };
155        assert_eq!(param.to_string(), "name");
156    }
157}