|
| 1 | +use ::std::collections::HashMap; |
| 2 | +use std::{ |
| 3 | + convert::From, |
| 4 | + fmt::{self, Display}, |
| 5 | +}; |
| 6 | + |
| 7 | +/// Represents a value in a query string, which can be either a single value or multiple values for the same key. |
| 8 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 9 | +pub enum Value<'buf> { |
| 10 | + Single(&'buf str), |
| 11 | + Multiple(Vec<&'buf str>), // heap allocated array, dynamically growing |
| 12 | +} |
| 13 | + |
| 14 | +/// Represents a parsed query string, storing key-value pairs where both key and value are string slices from the same buffer. |
| 15 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 16 | +pub struct QueryString<'buf> { |
| 17 | + // both key and value comes from the same buffer, so they have the same lifetime as `buf` |
| 18 | + data: HashMap<&'buf str, Value<'buf>>, |
| 19 | +} |
| 20 | +impl<'buf> QueryString<'buf> { |
| 21 | + /// Retrieves the value associated with the given key from the query string. |
| 22 | + /// |
| 23 | + /// # Arguments |
| 24 | + /// |
| 25 | + /// * `key` - The key to look up in the query string. |
| 26 | + /// |
| 27 | + /// # Returns |
| 28 | + /// |
| 29 | + /// An `Option` containing a reference to the `Value` if the key exists, or `None` otherwise. |
| 30 | + pub fn get(&self, key: &str) -> Option<&Value> { |
| 31 | + self.data.get(key) |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +/// Implements conversion from a string slice to a `QueryString`. |
| 36 | +/// |
| 37 | +/// # Note |
| 38 | +/// |
| 39 | +/// The `From` trait is used instead of `TryFrom` because the conversion from a string to a `QueryString` cannot fail. |
| 40 | +/// The input string buffer is assumed to have a valid format for query strings (e.g., `key1=value1&key2=value2`). |
| 41 | +impl<'buf> From<&'buf str> for QueryString<'buf> { |
| 42 | + /// Converts a string slice into a QueryString by parsing key-value pairs. |
| 43 | + /// |
| 44 | + /// # Arguments |
| 45 | + /// |
| 46 | + /// * `s` - A string slice containing the query string to parse (e.g., "key1=value1&key2=value2") |
| 47 | + /// |
| 48 | + /// # Returns |
| 49 | + /// |
| 50 | + /// A new `QueryString` instance containing the parsed key-value pairs. |
| 51 | + /// |
| 52 | + /// # Examples |
| 53 | + /// |
| 54 | + /// ``` |
| 55 | + /// let query = QueryString::from("name=John&age=30"); |
| 56 | + /// ``` |
| 57 | + fn from(s: &'buf str) -> Self { |
| 58 | + // Initialize an empty HashMap to store the key-value pairs |
| 59 | + let mut data = HashMap::new(); |
| 60 | + |
| 61 | + // Split the input string by '&' to get individual key-value pairs |
| 62 | + for sub_str in s.split('&') { |
| 63 | + // Default values in case no '=' is found |
| 64 | + let mut key = sub_str; |
| 65 | + let mut value = ""; |
| 66 | + |
| 67 | + // If '=' is found, split the substring into key and value |
| 68 | + if let Some(i) = sub_str.find('=') { |
| 69 | + key = &sub_str[..i]; |
| 70 | + value = &sub_str[i + 1..]; |
| 71 | + } |
| 72 | + |
| 73 | + // Insert the key-value pair into the HashMap |
| 74 | + data.entry(key) |
| 75 | + // Handle the case where a key already exists in the HashMap |
| 76 | + .and_modify(|existing: &mut Value| match existing { |
| 77 | + // If the key exists with a single value, convert it to a Multiple value |
| 78 | + Value::Single(prev_value) => { |
| 79 | + *existing = Value::Multiple(vec![prev_value, value]); // dereference the existing value and assign a new value |
| 80 | + } |
| 81 | + // If the key already has multiple values, append the new value |
| 82 | + Value::Multiple(vec) => vec.push(value), |
| 83 | + }) |
| 84 | + // If the key does not exist, insert a new single value |
| 85 | + .or_insert(Value::Single(value)); |
| 86 | + } |
| 87 | + |
| 88 | + // Create and return a new QueryString with the parsed data |
| 89 | + QueryString { data } |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +/// Implements the `Display` trait for `QueryString`, formatting it as a string of joined keys separated by '&'. |
| 94 | +impl<'buf> Display for QueryString<'buf> { |
| 95 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 96 | + write!( |
| 97 | + f, |
| 98 | + "{}", |
| 99 | + self.data |
| 100 | + .keys() |
| 101 | + .map(|key| key.to_string()) |
| 102 | + .collect::<Vec<String>>() |
| 103 | + .join("&") |
| 104 | + ) |
| 105 | + } |
| 106 | +} |
0 commit comments