1use std::fmt;
2use std::fmt::{Debug, Display, Formatter, Write};
3
4use crate::QUERY;
5use percent_encoding::utf8_percent_encode;
6
7pub type QueryStringSimple = WrappedQueryString<RootMarker, EmptyValue>;
9
10pub struct WrappedQueryString<B, T>
32where
33 B: ConditionalDisplay + Identifiable,
34 T: Display,
35{
36 base: BaseOption<B>,
37 value: KvpOption<T>,
38}
39
40impl Default for QueryStringSimple {
41 fn default() -> Self {
42 QueryStringSimple::new()
43 }
44}
45
46pub struct Kvp<K, V>
48where
49 K: Display,
50 V: Display,
51{
52 key: K,
53 value: V,
54}
55
56enum BaseOption<B> {
57 Some(B),
58 None,
59}
60
61enum KvpOption<T> {
62 Some(T),
63 None,
64}
65
66pub struct RootMarker(());
69
70pub struct EmptyValue(());
73
74impl<B, T> WrappedQueryString<B, T>
75where
76 B: ConditionalDisplay + Identifiable,
77 T: Display,
78{
79 pub(crate) fn new() -> WrappedQueryString<RootMarker, EmptyValue> {
81 WrappedQueryString {
82 base: BaseOption::None,
83 value: KvpOption::None,
84 }
85 }
86
87 pub fn with_value<K: Display, V: Display>(
105 self,
106 key: K,
107 value: V,
108 ) -> WrappedQueryString<Self, Kvp<K, V>> {
109 WrappedQueryString {
110 base: BaseOption::Some(self),
111 value: KvpOption::Some(Kvp { key, value }),
112 }
113 }
114
115 pub fn with_opt_value<K: Display, V: Display>(
134 self,
135 key: K,
136 value: Option<V>,
137 ) -> WrappedQueryString<Self, Kvp<K, V>> {
138 if let Some(value) = value {
139 WrappedQueryString {
140 base: BaseOption::Some(self),
141 value: KvpOption::Some(Kvp { key, value }),
142 }
143 } else {
144 WrappedQueryString {
145 base: BaseOption::Some(self),
146 value: KvpOption::None,
147 }
148 }
149 }
150
151 pub fn len(&self) -> usize {
153 if self.is_empty() {
154 return 0;
155 }
156
157 1 + self.base.len()
158 }
159
160 pub fn is_empty(&self) -> bool {
162 if self.is_root() && self.value.is_empty() {
164 return true;
165 }
166
167 if !self.value.is_empty() {
169 return false;
170 }
171
172 self.base.is_empty()
173 }
174}
175
176pub trait Identifiable {
177 fn is_root(&self) -> bool;
178 fn is_empty(&self) -> bool;
179 fn len(&self) -> usize;
180}
181
182pub trait ConditionalDisplay {
183 fn cond_fmt(&self, should_display: bool, f: &mut Formatter<'_>) -> Result<usize, fmt::Error>;
184}
185
186impl Identifiable for RootMarker {
187 fn is_root(&self) -> bool {
188 unreachable!()
189 }
190
191 fn is_empty(&self) -> bool {
192 unreachable!()
193 }
194
195 fn len(&self) -> usize {
196 unreachable!()
197 }
198}
199
200impl ConditionalDisplay for RootMarker {
201 fn cond_fmt(&self, _should_display: bool, _f: &mut Formatter<'_>) -> Result<usize, fmt::Error> {
202 unreachable!()
203 }
204}
205
206impl Display for RootMarker {
207 fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result {
208 unreachable!()
209 }
210}
211
212impl<B> ConditionalDisplay for BaseOption<B>
213where
214 B: ConditionalDisplay,
215{
216 fn cond_fmt(&self, should_display: bool, f: &mut Formatter<'_>) -> Result<usize, fmt::Error> {
217 match self {
218 BaseOption::Some(base) => Ok(base.cond_fmt(should_display, f)?),
219 BaseOption::None => {
220 if should_display {
222 f.write_char('?')?;
223 }
224 Ok(0)
225 }
226 }
227 }
228}
229
230impl<B, T> ConditionalDisplay for WrappedQueryString<B, T>
231where
232 B: ConditionalDisplay + Identifiable,
233 T: Display,
234{
235 fn cond_fmt(&self, should_display: bool, f: &mut Formatter<'_>) -> Result<usize, fmt::Error> {
236 let depth = if !should_display {
237 if self.value.is_empty() {
240 return self.base.cond_fmt(false, f);
241 }
242
243 self.base.cond_fmt(true, f)?
245 } else {
246 self.base.cond_fmt(true, f)?
248 };
249
250 if self.value.is_empty() {
252 return Ok(depth);
253 }
254
255 self.value.fmt(f)?;
257
258 if should_display {
260 f.write_char('&')?;
261 }
262
263 Ok(depth + 1)
264 }
265}
266
267impl<B> BaseOption<B>
268where
269 B: Identifiable + ConditionalDisplay,
270{
271 fn is_empty(&self) -> bool {
272 match self {
273 BaseOption::Some(value) => value.is_empty(),
274 BaseOption::None => true,
275 }
276 }
277
278 fn len(&self) -> usize {
279 match self {
280 BaseOption::Some(value) => value.len(),
281 BaseOption::None => 0,
282 }
283 }
284}
285
286impl<B, T> Identifiable for WrappedQueryString<B, T>
287where
288 B: ConditionalDisplay + Identifiable,
289 T: Display,
290{
291 fn is_root(&self) -> bool {
292 match self.base {
293 BaseOption::Some(_) => false,
294 BaseOption::None => true,
295 }
296 }
297
298 fn is_empty(&self) -> bool {
299 match self.value {
300 KvpOption::Some(_) => false,
301 KvpOption::None => self.base.is_empty(),
302 }
303 }
304
305 fn len(&self) -> usize {
306 match self.value {
307 KvpOption::Some(_) => 1 + self.base.len(),
308 KvpOption::None => self.base.len(),
309 }
310 }
311}
312
313impl<T> KvpOption<T> {
314 fn is_empty(&self) -> bool {
315 match self {
316 KvpOption::Some(_) => false,
317 KvpOption::None => true,
318 }
319 }
320}
321
322impl Display for EmptyValue {
323 fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result {
324 Ok(())
325 }
326}
327
328impl<T> Display for BaseOption<T>
329where
330 T: Display,
331{
332 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
333 match self {
334 BaseOption::Some(d) => Display::fmt(d, f),
335 BaseOption::None => Ok(()),
336 }
337 }
338}
339
340impl<T> Display for KvpOption<T>
341where
342 T: Display,
343{
344 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
345 match self {
346 KvpOption::Some(d) => Display::fmt(d, f),
347 KvpOption::None => Ok(()),
348 }
349 }
350}
351
352impl<K, V> Display for Kvp<K, V>
353where
354 K: Display,
355 V: Display,
356{
357 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
358 Display::fmt(&utf8_percent_encode(&self.key.to_string(), QUERY), f)?;
359 f.write_char('=')?;
360 Display::fmt(&utf8_percent_encode(&self.value.to_string(), QUERY), f)
361 }
362}
363
364impl<B, T> Display for WrappedQueryString<B, T>
365where
366 B: ConditionalDisplay + Identifiable,
367 T: Display,
368{
369 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
370 let should_display = !self.value.is_empty();
371
372 self.base.cond_fmt(should_display, f)?;
373 if should_display {
374 Display::fmt(&self.value, f)?;
375 }
376
377 Ok(())
378 }
379}
380
381impl<B, T> Debug for WrappedQueryString<B, T>
382where
383 B: ConditionalDisplay + Identifiable,
384 T: Display,
385{
386 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
387 Display::fmt(self, f)
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use crate::slim::{BaseOption, EmptyValue, KvpOption};
394 use crate::QueryString;
395
396 #[test]
397 fn test_empty() {
398 let qs = QueryString::simple();
399
400 assert!(qs.is_empty());
401 assert_eq!(qs.len(), 0);
402
403 assert_eq!(qs.to_string(), "");
404 }
405
406 #[test]
407 fn test_empty_complex() {
408 let qs = QueryString::simple().with_opt_value("key", None::<&str>);
409
410 assert!(qs.is_empty());
411 assert_eq!(qs.len(), 0);
412
413 assert_eq!(qs.to_string(), "");
414 }
415
416 #[test]
417 fn test_simple() {
418 let apple = "apple???";
419
420 let qs = QueryString::simple()
421 .with_value("q", &apple)
422 .with_value("category", "fruits and vegetables")
423 .with_value("tasty", true)
424 .with_value("weight", 99.9);
425
426 assert!(!qs.is_empty());
427 assert_eq!(qs.len(), 4);
428
429 assert_eq!(
430 format!("{qs}"),
431 "?q=apple???&category=fruits%20and%20vegetables&tasty=true&weight=99.9"
432 );
433 }
434
435 #[test]
436 fn test_encoding() {
437 let qs = QueryString::simple()
438 .with_value("q", "Grünkohl")
439 .with_value("category", "Gemüse");
440
441 assert!(!qs.is_empty());
442 assert_eq!(qs.len(), 2);
443
444 assert_eq!(qs.to_string(), "?q=Gr%C3%BCnkohl&category=Gem%C3%BCse");
445 }
446
447 #[test]
448 fn test_emoji() {
449 let qs = QueryString::simple()
450 .with_value("q", "🥦")
451 .with_value("🍽️", "🍔🍕");
452
453 assert!(!qs.is_empty());
454 assert_eq!(qs.len(), 2);
455
456 assert_eq!(
457 format!("{qs:?}"),
458 "?q=%F0%9F%A5%A6&%F0%9F%8D%BD%EF%B8%8F=%F0%9F%8D%94%F0%9F%8D%95"
459 );
460 }
461
462 #[test]
463 fn test_optional() {
464 let qs = QueryString::simple()
465 .with_value("q", "celery")
466 .with_opt_value("taste", None::<String>)
467 .with_opt_value("category", Some("fruits and vegetables"))
468 .with_opt_value("tasty", Some(true))
469 .with_opt_value("weight", Some(99.9));
470
471 assert!(!qs.is_empty());
472 assert_eq!(qs.len(), 4);
473
474 assert_eq!(
475 qs.to_string(),
476 "?q=celery&category=fruits%20and%20vegetables&tasty=true&weight=99.9"
477 );
478 assert_eq!(qs.len(), 4); }
480
481 #[test]
482 fn test_display() {
483 assert_eq!(format!("{}", KvpOption::<i32>::None), "");
484 assert_eq!(format!("{}", BaseOption::<i32>::None), "");
485 assert_eq!(format!("{}", EmptyValue(())), "");
486 }
487}