Skip to main content

sift_query_core/protocol/raw/
mod.rs

1//! Ordered, multi-value raw parameters with bounded URL encoding.
2use crate::{ParamError, QueryLimits};
3use sift_query_algorithm::BoundedCounter;
4use url::{decode_component, encode_component, encoded_len};
5mod url;
6
7/// Decoded, ordered, multi-value query parameters.
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
9pub struct RawParams {
10    entries: Vec<(String, String)>,
11    raw_bytes: usize,
12    decoded_bytes: usize,
13}
14
15impl RawParams {
16    pub fn parse(query: &str) -> Result<Self, ParamError> {
17        Self::parse_with_limits(query, &QueryLimits::default())
18    }
19
20    pub fn parse_with_limits(query: &str, limits: &QueryLimits) -> Result<Self, ParamError> {
21        if query.len() > limits.max_raw_query_bytes {
22            return Err(ParamError::QueryTooLong);
23        }
24        if query.is_empty() {
25            return Ok(Self::default());
26        }
27
28        let mut entries = Vec::new();
29        let mut decoded_bytes = BoundedCounter::new(limits.max_decoded_bytes);
30        for item in query.split('&') {
31            if entries.len() >= limits.max_raw_items {
32                return Err(ParamError::TooComplex("raw parameter item count"));
33            }
34            if item.is_empty() {
35                return Err(ParamError::InvalidName(String::new()));
36            }
37            let (raw_key, raw_value) = item.split_once('=').unwrap_or((item, ""));
38            let key = decode_component(raw_key)?;
39            let value = decode_component(raw_value)?;
40            if key.is_empty() || key.len() > limits.max_key_bytes {
41                return Err(ParamError::InvalidName(key));
42            }
43            if value.len() > limits.max_value_bytes {
44                return Err(ParamError::TooComplex("single parameter value length"));
45            }
46            decoded_bytes
47                .try_add(key.len())
48                .and_then(|()| decoded_bytes.try_add(value.len()))
49                .map_err(|_| ParamError::TooComplex("decoded parameter bytes"))?;
50            entries.push((key, value));
51        }
52        Ok(Self {
53            entries,
54            raw_bytes: query.len(),
55            decoded_bytes: decoded_bytes.used(),
56        })
57    }
58
59    /// Builds ordered, repeated parameters produced by a trusted normalization
60    /// filter. The same byte and item limits as URL parsing are enforced.
61    pub fn from_entries(
62        entries: impl IntoIterator<Item = (String, String)>,
63        limits: &QueryLimits,
64    ) -> Result<Self, ParamError> {
65        let mut output = Vec::new();
66        let mut decoded_bytes = BoundedCounter::new(limits.max_decoded_bytes);
67        let mut raw_bytes = 0usize;
68        for (key, value) in entries {
69            if output.len() >= limits.max_raw_items {
70                return Err(ParamError::TooComplex("filtered parameter item count"));
71            }
72            if key.is_empty() || key.len() > limits.max_key_bytes {
73                return Err(ParamError::InvalidName(key.clone()));
74            }
75            if value.len() > limits.max_value_bytes {
76                return Err(ParamError::TooComplex("filtered parameter value length"));
77            }
78            decoded_bytes
79                .try_add(key.len())
80                .and_then(|()| decoded_bytes.try_add(value.len()))
81                .map_err(|_| ParamError::TooComplex("filtered parameter bytes"))?;
82            raw_bytes = raw_bytes
83                .saturating_add(encoded_len(&key))
84                .saturating_add(encoded_len(&value))
85                .saturating_add(if output.is_empty() { 1 } else { 2 });
86            if raw_bytes > limits.max_raw_query_bytes {
87                return Err(ParamError::QueryTooLong);
88            }
89            output.push((key, value));
90        }
91        Ok(Self {
92            entries: output,
93            raw_bytes,
94            decoded_bytes: decoded_bytes.used(),
95        })
96    }
97
98    #[must_use]
99    pub fn entries(&self) -> &[(String, String)] {
100        &self.entries
101    }
102
103    /// Encodes this ordered, repeated parameter set as a stable URL query.
104    #[must_use]
105    pub fn to_query_string(&self) -> String {
106        let mut output = String::with_capacity(self.raw_bytes);
107        for (index, (key, value)) in self.entries.iter().enumerate() {
108            if index > 0 {
109                output.push('&');
110            }
111            encode_component(key, &mut output);
112            output.push('=');
113            encode_component(value, &mut output);
114        }
115        output
116    }
117
118    #[must_use]
119    pub fn len(&self) -> usize {
120        self.entries.len()
121    }
122
123    #[must_use]
124    pub fn is_empty(&self) -> bool {
125        self.entries.is_empty()
126    }
127
128    #[must_use]
129    pub const fn raw_bytes(&self) -> usize {
130        self.raw_bytes
131    }
132
133    #[must_use]
134    pub const fn decoded_bytes(&self) -> usize {
135        self.decoded_bytes
136    }
137}