1use std::{borrow::Cow, convert::Infallible, ops};
4
5use macro_toolset::{
6 md5, str_concat,
7 string::{general::tuple::SeplessTuple, PushAnyT, StringExtT},
8 urlencoding_str,
9};
10
11#[deprecated(
12 since = "0.6.0",
13 note = "Renamed and deprecated, use [`Query`] instead."
14)]
15pub type Queries<'q> = Query<'q>;
17
18#[derive(Debug)]
19#[repr(transparent)]
20pub struct Query<'q> {
22 inner: Vec<(Cow<'q, str>, Cow<'q, str>)>,
23}
24
25impl<'q> ops::Deref for Query<'q> {
26 type Target = Vec<(Cow<'q, str>, Cow<'q, str>)>;
27
28 #[inline]
29 fn deref(&self) -> &Self::Target {
30 &self.inner
31 }
32}
33
34impl Default for Query<'_> {
35 fn default() -> Self {
36 Self::with_capacity(4)
37 }
38}
39
40impl<'q> Query<'q> {
41 #[inline]
42 pub const fn new() -> Self {
47 Self { inner: Vec::new() }
48 }
49
50 #[inline]
51 pub fn with_capacity(capacity: usize) -> Self {
53 Self {
54 inner: Vec::with_capacity(capacity),
55 }
56 }
57
58 #[inline]
59 pub fn push(mut self, key: impl Into<Cow<'q, str>>, value: impl Into<Cow<'q, str>>) -> Self {
61 self.inner.push((key.into(), value.into()));
62 self
63 }
64
65 #[inline]
66 pub fn push_any(mut self, key: impl Into<Cow<'q, str>>, value: impl StringExtT) -> Self {
71 self.inner.push((key.into(), value.to_string_ext().into()));
72 self
73 }
74
75 #[inline]
76 pub fn sort(&mut self) {
80 self.inner.sort_unstable_by(|l, r| l.0.cmp(&r.0));
81 }
82
83 #[inline]
84 pub fn sorted(mut self) -> Self {
86 self.sort();
87 self
88 }
89
90 #[inline]
91 pub const fn inner(&self) -> &Vec<(Cow<'q, str>, Cow<'q, str>)> {
93 &self.inner
94 }
95
96 #[inline]
97 pub fn into_inner(self) -> Vec<(Cow<'q, str>, Cow<'q, str>)> {
99 self.inner
100 }
101
102 #[inline]
103 pub fn intercept<F>(mut self, f: F) -> Self
105 where
106 F: Fn(&mut Self),
107 {
108 f(&mut self);
109
110 self
111 }
112
113 #[inline]
114 pub fn intercept_fallible<F, E>(mut self, f: F) -> Result<Self, E>
116 where
117 F: Fn(&mut Self) -> Result<(), E>,
118 {
119 f(&mut self)?;
120
121 Ok(self)
122 }
123
124 pub fn batch_intercept_fallible<I, E>(mut self, interceptors: I) -> Result<Self, E>
128 where
129 I: Iterator,
130 I::Item: Fn(&mut Self) -> Result<(), E>,
131 {
132 for f in interceptors {
133 f(&mut self)?;
134 }
135
136 Ok(self)
137 }
138
139 #[inline]
140 pub fn build(self) -> String {
142 str_concat!(sep = "&"; self.inner.iter().map(|(k, v)| {
143 SeplessTuple::new((k, "=", urlencoding_str!(E: v)))
144 }))
145 }
146
147 #[inline]
148 pub fn build_signed<S: SignerT>(self, signer: S) -> Result<String, S::Error> {
150 signer.build_signed(self)
151 }
152}
153
154pub trait SignerT {
156 type Error;
158
159 fn build_signed(self, query: Query) -> Result<String, Self::Error>;
161}
162
163#[derive(Debug, Clone, Copy)]
164pub struct Md5Signer<'s> {
166 pub query_key: &'s str,
170
171 pub prefix_salt: Option<&'s str>,
173
174 pub suffix_salt: Option<&'s str>,
176}
177
178impl Default for Md5Signer<'_> {
179 fn default() -> Self {
180 Self {
181 query_key: "sign",
182 prefix_salt: None,
183 suffix_salt: None,
184 }
185 }
186}
187
188impl SignerT for Md5Signer<'_> {
189 type Error = Infallible;
190
191 fn build_signed(self, query: Query) -> Result<String, Self::Error> {
192 let query = query.sorted();
193
194 let mut final_string_buf = String::with_capacity(64);
195
196 final_string_buf.push_any_with_separator(
197 query
198 .inner
199 .iter()
200 .map(|(k, v)| SeplessTuple::new((k, "=", urlencoding_str!(E: v)))),
201 "&",
202 );
203
204 let signed = match (self.prefix_salt, self.suffix_salt) {
205 (None, Some(suffix_salt)) => md5!(final_string_buf, suffix_salt), (None, None) => md5!(final_string_buf),
207 (Some(prefix_salt), Some(suffix_salt)) => {
208 md5!(prefix_salt, final_string_buf, suffix_salt)
209 }
210 (Some(prefix_salt), None) => md5!(prefix_salt, final_string_buf),
211 };
212
213 if final_string_buf.is_empty() {
214 final_string_buf.push_any((self.query_key, "=", signed.as_str()));
215 } else {
216 final_string_buf.push_any(("&", self.query_key, "=", signed.as_str()));
217 }
218
219 Ok(final_string_buf)
220 }
221}
222
223impl<'s> Md5Signer<'s> {
224 #[inline]
225 pub const fn new(
227 query_key: &'s str,
228 prefix_salt: Option<&'s str>,
229 suffix_salt: Option<&'s str>,
230 ) -> Self {
231 Self {
232 query_key,
233 prefix_salt,
234 suffix_salt,
235 }
236 }
237
238 #[inline]
239 pub const fn new_default() -> Self {
241 Self {
242 query_key: "sign",
243 prefix_salt: None,
244 suffix_salt: None,
245 }
246 }
247
248 #[inline]
249 pub const fn with_query_key(self, query_key: &'s str) -> Self {
251 Self { query_key, ..self }
252 }
253
254 #[inline]
255 pub const fn with_prefix_salt(self, prefix_salt: Option<&'s str>) -> Self {
257 Self {
258 prefix_salt,
259 ..self
260 }
261 }
262
263 #[inline]
264 pub const fn with_suffix_salt(self, suffix_salt: Option<&'s str>) -> Self {
266 Self {
267 suffix_salt,
268 ..self
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_general() {
279 let query = Query::with_capacity(16)
280 .push_any("test1", 1)
281 .push_any("test2", "2")
282 .build_signed(Md5Signer::new_default().with_suffix_salt(Some("0123456789abcdef")))
283 .unwrap();
284
285 assert_eq!(
286 query,
287 "test1=1&test2=2&sign=cc4f5844a6a1893a88d648cebba5462f"
288 )
289 }
290}