Skip to main content

use_query/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4/// A parsed query parameter with an optional value.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct QueryParam {
7    pub key: String,
8    pub value: Option<String>,
9}
10
11/// Parses a query string into ordered key-value pairs.
12#[must_use]
13pub fn parse_query(input: &str) -> Vec<QueryParam> {
14    let query = normalize_query_input(input);
15    if query.is_empty() {
16        return Vec::new();
17    }
18
19    query
20        .split('&')
21        .filter(|segment| !segment.is_empty())
22        .map(|segment| match segment.split_once('=') {
23            Some((key, value)) => QueryParam {
24                key: key.to_string(),
25                value: Some(value.to_string()),
26            },
27            None => QueryParam {
28                key: segment.to_string(),
29                value: None,
30            },
31        })
32        .collect()
33}
34
35/// Builds a query string from ordered parameters.
36#[must_use]
37pub fn build_query(params: &[QueryParam]) -> String {
38    params
39        .iter()
40        .map(|param| match &param.value {
41            Some(value) => format!("{}={value}", param.key),
42            None => param.key.clone(),
43        })
44        .collect::<Vec<_>>()
45        .join("&")
46}
47
48/// Returns the first value for the requested key.
49#[must_use]
50pub fn get_query_param(input: &str, key: &str) -> Option<String> {
51    parse_query(input)
52        .into_iter()
53        .find(|param| param.key == key)
54        .and_then(|param| param.value)
55}
56
57/// Returns `true` when the query contains the requested key.
58#[must_use]
59pub fn has_query_param(input: &str, key: &str) -> bool {
60    parse_query(input).into_iter().any(|param| param.key == key)
61}
62
63/// Removes every matching key from the query string.
64#[must_use]
65pub fn remove_query_param(input: &str, key: &str) -> String {
66    let filtered = parse_query(input)
67        .into_iter()
68        .filter(|param| param.key != key)
69        .collect::<Vec<_>>();
70
71    build_query(&filtered)
72}
73
74/// Appends a key-value pair to the query string.
75#[must_use]
76pub fn append_query_param(input: &str, key: &str, value: &str) -> String {
77    let mut params = parse_query(input);
78    params.push(QueryParam {
79        key: key.to_string(),
80        value: Some(value.to_string()),
81    });
82    build_query(&params)
83}
84
85fn normalize_query_input(input: &str) -> &str {
86    let trimmed = input.trim();
87    let before_fragment = trimmed
88        .split_once('#')
89        .map_or(trimmed, |(before, _)| before);
90
91    if let Some((_, query)) = before_fragment.split_once('?') {
92        query
93    } else {
94        before_fragment.strip_prefix('?').unwrap_or(before_fragment)
95    }
96}