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 #[inline]
28 fn deref(&self) -> &Self::Target {
29 &self.inner
30 }
31}
32
33impl<'q> Query<'q> {
34 #[inline]
35 pub fn with_capacity(capacity: usize) -> Self {
37 Self {
38 inner: Vec::with_capacity(capacity),
39 }
40 }
41
42 #[inline]
43 pub fn push(mut self, key: impl Into<Cow<'q, str>>, value: impl Into<Cow<'q, str>>) -> Self {
45 self.inner.push((key.into(), value.into()));
46 self
47 }
48
49 #[inline]
50 pub fn push_any(mut self, key: impl Into<Cow<'q, str>>, value: impl StringExtT) -> Self {
52 self.inner.push((key.into(), value.to_string_ext().into()));
53 self
54 }
55
56 #[inline]
57 pub fn sorted(mut self) -> Self {
59 self.inner.sort_unstable_by(|l, r| l.0.cmp(&r.0));
60 self
61 }
62
63 #[inline]
64 pub const fn inner(&self) -> &Vec<(Cow<'q, str>, Cow<'q, str>)> {
66 &self.inner
67 }
68
69 #[inline]
70 pub fn into_inner(self) -> Vec<(Cow<'q, str>, Cow<'q, str>)> {
72 self.inner
73 }
74
75 #[inline]
76 pub fn build(self) -> String {
78 str_concat!(sep = "&"; self.inner.iter().map(|(k, v)| {
79 (k, "=", urlencoding_str!(E: v))
80 }))
81 }
82
83 #[inline]
84 pub fn build_signed<S: SignerT>(self, signer: S) -> Result<String, S::Error> {
86 signer.build_signed(self)
87 }
88}
89
90pub trait SignerT {
92 type Error;
94
95 fn build_signed(self, query: Query) -> Result<String, Self::Error>;
97}
98
99#[derive(Debug, Clone, Copy)]
100pub struct Md5Signer<'s> {
102 pub query_key: &'s str,
106
107 pub prefix_salt: Option<&'s str>,
109
110 pub suffix_salt: Option<&'s str>,
112}
113
114impl Default for Md5Signer<'_> {
115 fn default() -> Self {
116 Self {
117 query_key: "sign",
118 prefix_salt: None,
119 suffix_salt: None,
120 }
121 }
122}
123
124impl SignerT for Md5Signer<'_> {
125 type Error = Infallible;
126
127 fn build_signed(self, query: Query) -> Result<String, Self::Error> {
128 let query = query.sorted();
129
130 let mut final_string_buf = String::with_capacity(64);
131
132 final_string_buf.push_any_with_separator(
133 query
134 .inner
135 .iter()
136 .map(|(k, v)| SeplessTuple::new((k, "=", urlencoding_str!(E: v)))),
137 "&",
138 );
139
140 let signed = match (self.prefix_salt, self.suffix_salt) {
141 (None, Some(suffix_salt)) => md5!(final_string_buf, suffix_salt), (None, None) => md5!(final_string_buf),
143 (Some(prefix_salt), Some(suffix_salt)) => {
144 md5!(prefix_salt, final_string_buf, suffix_salt)
145 }
146 (Some(prefix_salt), None) => md5!(prefix_salt, final_string_buf),
147 };
148
149 if final_string_buf.is_empty() {
150 final_string_buf.push_any((self.query_key, "=", signed.as_str()));
151 } else {
152 final_string_buf.push_any(("&", self.query_key, "=", signed.as_str()));
153 }
154
155 Ok(final_string_buf)
156 }
157}
158
159impl<'s> Md5Signer<'s> {
160 #[inline]
161 pub const fn new(
163 query_key: &'s str,
164 prefix_salt: Option<&'s str>,
165 suffix_salt: Option<&'s str>,
166 ) -> Self {
167 Self {
168 query_key,
169 prefix_salt,
170 suffix_salt,
171 }
172 }
173
174 #[inline]
175 pub const fn new_default() -> Self {
177 Self {
178 query_key: "sign",
179 prefix_salt: None,
180 suffix_salt: None,
181 }
182 }
183
184 #[inline]
185 pub const fn with_query_key(self, query_key: &'s str) -> Self {
187 Self { query_key, ..self }
188 }
189
190 #[inline]
191 pub const fn with_prefix_salt(self, prefix_salt: Option<&'s str>) -> Self {
193 Self {
194 prefix_salt,
195 ..self
196 }
197 }
198
199 #[inline]
200 pub const fn with_suffix_salt(self, suffix_salt: Option<&'s str>) -> Self {
202 Self {
203 suffix_salt,
204 ..self
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_general() {
215 let query = Query::with_capacity(16)
216 .push_any("test1", 1)
217 .push_any("test2", "2")
218 .build_signed(Md5Signer::new_default().with_suffix_salt(Some("0123456789abcdef")))
219 .unwrap();
220
221 assert_eq!(
222 query,
223 "test1=1&test2=2&sign=cc4f5844a6a1893a88d648cebba5462f"
224 )
225 }
226}