1use crate::error::{Error, Result};
2#[cfg(feature = "sql")]
3use crate::sql;
4use indexmap::IndexMap;
5use std::fmt;
6use std::str::FromStr;
7use url::form_urlencoded;
8
9#[derive(Clone, Debug, PartialEq)]
11pub struct Query {
12 pub parameters: Parameters,
13 pub order: Order,
14 pub limit: usize,
15 pub offset: usize,
16}
17
18impl Query {
19 pub fn new() -> Self {
20 Self {
21 parameters: Parameters::new(),
22 order: Order::new(),
23 limit: Parameters::DEFAULT_LIMIT,
24 offset: Parameters::DEFAULT_OFFSET,
25 }
26 }
27
28 pub fn init(parameters: Parameters, order: Order, limit: usize, offset: usize) -> Self {
29 Self {
30 parameters,
31 order,
32 limit,
33 offset,
34 }
35 }
36
37 #[cfg(feature = "http")]
38 pub fn to_http(&self) -> String {
39 let params_str = format!("{}", self.parameters);
40 let order_str = format!("{}", self.order);
41
42 let mut result = String::new();
43
44 if !params_str.is_empty() {
45 result.push_str(¶ms_str);
46 result.push(AMPERSAND);
47 }
48
49 if !order_str.is_empty() {
50 result.push_str(&format!("{}{EQUAL}{}", Parameters::ORDER, order_str));
51 result.push(AMPERSAND);
52 }
53
54 let pagination_str = format!(
55 "{}{EQUAL}{}{AMPERSAND}{}{EQUAL}{}",
56 Parameters::LIMIT,
57 self.limit,
58 Parameters::OFFSET,
59 self.offset,
60 );
61
62 result.push_str(&pagination_str);
63 result
64 }
65
66 #[cfg(feature = "http")]
68 pub fn from_http(search: String) -> Result<Self> {
69 let mut query = Self::new();
70 let trimmed_search = search.trim_start_matches(QUESTION).trim();
71
72 if trimmed_search.is_empty() {
73 return Ok(query);
74 }
75
76 for k_v in trimmed_search.split(AMPERSAND) {
77 let trimmed_kv = k_v.trim();
78 if trimmed_kv.is_empty() {
79 continue;
80 }
81
82 let mut parts = trimmed_kv.splitn(2, EQUAL);
83 if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
84 let trimmed_key = key.trim();
85 let trimmed_value = value.trim();
86
87 if trimmed_key.is_empty() || trimmed_value.is_empty() {
88 continue;
89 }
90
91 match trimmed_key {
92 Parameters::ORDER => {
93 if !trimmed_value.contains(COLON) {
95 return Err(Error::InvalidOrderField(trimmed_value.into()));
97 }
98
99 if let Ok(order) = trimmed_value.parse::<Order>() {
100 query.order = order;
101 }
102 }
104 Parameters::LIMIT => {
105 query.limit = trimmed_value.parse().unwrap_or(Parameters::DEFAULT_LIMIT);
106 }
107 Parameters::OFFSET => {
108 query.offset = trimmed_value.parse().unwrap_or(Parameters::DEFAULT_OFFSET);
109 }
110 _k => {
111 if trimmed_value.contains(COLON) {
113 let param = trimmed_value.parse::<Parameter>()?;
115 if param.values().is_empty() {
117 continue;
118 }
119 query.parameters.0.insert(trimmed_key.to_string(), param);
121 } else {
122 let decoded_value = url_decode(trimmed_value);
124
125 if let Some(existing_param) =
127 query.parameters.0.get_mut(&trimmed_key.to_string())
128 {
129 if *existing_param.similarity() == Similarity::Equals {
131 existing_param.1.push(decoded_value);
132 }
133 } else {
135 query.parameters.0.insert(
137 trimmed_key.to_string(),
138 Parameter::init(Similarity::Equals, vec![decoded_value]),
139 );
140 }
141 }
142 }
143 }
144 } else {
145 return Err(Error::InvalidSearchParameters(search));
146 }
147 }
148
149 Ok(query)
150 }
151
152 #[cfg(feature = "sql")]
153 pub fn to_sql(&self) -> String {
154 let mut sql_parts = Vec::new();
155
156 if let Some(where_clause) = self.where_clause() {
158 sql_parts.push(format!("WHERE {}", where_clause));
159 }
160
161 if let Some(order_clause) = self.order_clause() {
163 sql_parts.push(format!("ORDER BY {}", order_clause));
164 }
165
166 sql_parts.push(format!("LIMIT ? OFFSET ?"));
168
169 sql_parts.join(" ")
170 }
171
172 #[cfg(feature = "sql")]
173 pub fn where_clause(&self) -> Option<String> {
174 let mut conditions = Vec::new();
175
176 for (key, param) in &self.parameters.0 {
177 let similarity = param.similarity();
178 let values = param.values();
179 if values.is_empty() {
180 continue;
181 }
182
183 let condition = match similarity {
184 Similarity::Equals => {
185 if values.len() == 1 {
186 if values[0] == sql::NULL {
187 format!("{} IS ?", key)
188 } else {
189 format!("{} = ?", key)
190 }
191 } else {
192 let placeholders = vec!["?"; values.len()].join(", ");
193 format!("{} IN ({})", key, placeholders)
194 }
195 }
196 Similarity::Contains => {
197 if values.len() == 1 {
198 format!("{} LIKE ?", key)
199 } else {
200 let like_conditions: Vec<String> =
201 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
202 format!("({})", like_conditions.join(" OR "))
203 }
204 }
205 Similarity::StartsWith => {
206 if values.len() == 1 {
207 format!("{} LIKE ?", key)
208 } else {
209 let like_conditions: Vec<String> =
210 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
211 format!("({})", like_conditions.join(" OR "))
212 }
213 }
214 Similarity::EndsWith => {
215 if values.len() == 1 {
216 format!("{} LIKE ?", key)
217 } else {
218 let like_conditions: Vec<String> =
219 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
220 format!("({})", like_conditions.join(" OR "))
221 }
222 }
223 Similarity::Between => {
224 if values.len() >= 2 {
225 let pairs: Vec<&[String]> = values.chunks(2).collect();
227 let between_conditions: Vec<String> = pairs
228 .iter()
229 .map(|pair| {
230 if pair.len() == 2 {
231 format!("{} BETWEEN ? AND ?", key)
232 } else {
233 String::new() }
235 })
236 .filter(|condition| !condition.is_empty())
237 .collect();
238
239 if between_conditions.is_empty() {
240 continue; } else if between_conditions.len() == 1 {
242 between_conditions[0].clone()
243 } else {
244 format!("({})", between_conditions.join(" OR "))
245 }
246 } else {
247 continue; }
249 }
250 Similarity::Lesser => {
251 if values.len() == 1 {
252 format!("{} < ?", key)
253 } else {
254 let conditions: Vec<String> =
255 values.iter().map(|_| format!("{} < ?", key)).collect();
256 format!("({})", conditions.join(" OR "))
257 }
258 }
259 Similarity::LesserOrEqual => {
260 if values.len() == 1 {
261 format!("{} <= ?", key)
262 } else {
263 let conditions: Vec<String> =
264 values.iter().map(|_| format!("{} <= ?", key)).collect();
265 format!("({})", conditions.join(" OR "))
266 }
267 }
268 Similarity::Greater => {
269 if values.len() == 1 {
270 format!("{} > ?", key)
271 } else {
272 let conditions: Vec<String> =
273 values.iter().map(|_| format!("{} > ?", key)).collect();
274 format!("({})", conditions.join(" OR "))
275 }
276 }
277 Similarity::GreaterOrEqual => {
278 if values.len() == 1 {
279 format!("{} >= ?", key)
280 } else {
281 let conditions: Vec<String> =
282 values.iter().map(|_| format!("{} >= ?", key)).collect();
283 format!("({})", conditions.join(" OR "))
284 }
285 }
286 };
287
288 conditions.push(condition);
289 }
290
291 if conditions.is_empty() {
292 None
293 } else {
294 Some(conditions.join(" AND "))
295 }
296 }
297
298 #[cfg(feature = "sql")]
299 pub fn order_clause(&self) -> Option<String> {
300 let mut order_parts = Vec::new();
301
302 for (name, direction) in &self.order.0 {
303 if !name.is_empty() {
304 let direction = match direction {
305 SortDirection::Ascending => "ASC",
306 SortDirection::Descending => "DESC",
307 };
308 order_parts.push(format!("{} {}", name, direction));
309 }
310 }
311
312 if order_parts.is_empty() {
313 None
314 } else {
315 Some(order_parts.join(", "))
316 }
317 }
318
319 #[cfg(feature = "sql")]
320 pub fn to_values(&self) -> Vec<sql::Value> {
321 let mut sql_values = self.parameter_values();
322 sql_values.extend(self.pagination_values());
323 sql_values
324 }
325
326 #[cfg(feature = "sql")]
327 pub fn parameter_values(&self) -> Vec<sql::Value> {
329 let mut sql_values = Vec::new();
330
331 for (_k, param) in self.parameters.inner() {
332 let param_similarity = param.similarity();
333 let param_values = param.values();
334 for cur_val in param_values {
335 if cur_val.trim().is_empty() {
337 continue;
338 }
339
340 if cur_val == sql::NULL {
341 sql_values.push(sql::Value::Null);
342 continue;
343 }
344
345 let sql_value = match *param_similarity {
346 Similarity::Contains => sql::Value::Text(format!("%{}%", cur_val)),
347 Similarity::StartsWith => sql::Value::Text(format!("{}%", cur_val)),
348 Similarity::EndsWith => sql::Value::Text(format!("%{}", cur_val)),
349 _ => {
350 if let Ok(i) = cur_val.parse::<i64>() {
352 sql::Value::Integer(i)
353 } else if let Ok(f) = cur_val.parse::<f64>() {
354 sql::Value::Real(f)
355 } else {
356 sql::Value::Text(cur_val.clone())
357 }
358 }
359 };
360
361 sql_values.push(sql_value);
362 }
363 }
364
365 sql_values
366 }
367
368 #[cfg(feature = "sql")]
369 pub fn pagination_values(&self) -> Vec<sql::Value> {
371 vec![
372 sql::Value::Integer(self.limit as i64),
373 sql::Value::Integer(self.offset as i64),
374 ]
375 }
376
377 #[cfg(feature = "sql")]
378 pub fn total_parameters(&self) -> usize {
381 let parameter_count: usize = self
382 .parameters
383 .inner()
384 .values()
385 .map(|param| {
386 param
387 .values()
388 .iter()
389 .filter(|v| !v.trim().is_empty())
390 .count()
391 })
392 .sum();
393
394 parameter_count + 2 }
396}
397
398#[derive(Clone, Debug, PartialEq)]
399pub struct Parameters(IndexMap<String, Parameter>);
400
401impl Parameters {
402 pub const ORDER: &str = "order";
403 pub const LIMIT: &str = "limit";
404 pub const OFFSET: &str = "offset";
405
406 pub const EXCLUDE: [&str; 3] = [Parameters::ORDER, Parameters::LIMIT, Parameters::OFFSET];
407
408 pub const DEFAULT_LIMIT: usize = 50;
409 pub const DEFAULT_OFFSET: usize = 0;
410
411 pub fn new() -> Self {
412 Self(IndexMap::new())
413 }
414
415 pub fn inner(&self) -> &IndexMap<String, Parameter> {
416 &self.0
417 }
418
419 pub fn inner_mut(&mut self) -> &mut IndexMap<String, Parameter> {
420 &mut self.0
421 }
422
423 pub fn equals(&mut self, key: String, values: Vec<String>) -> &mut Self {
424 self.0
425 .insert(key, Parameter::init(Similarity::Equals, values));
426 self
427 }
428
429 pub fn contains(&mut self, key: String, values: Vec<String>) -> &mut Self {
430 self.0
431 .insert(key, Parameter::init(Similarity::Contains, values));
432 self
433 }
434
435 pub fn starts_with(&mut self, key: String, values: Vec<String>) -> &mut Self {
436 self.0
437 .insert(key, Parameter::init(Similarity::StartsWith, values));
438 self
439 }
440
441 pub fn ends_with(&mut self, key: String, values: Vec<String>) -> &mut Self {
442 self.0
443 .insert(key, Parameter::init(Similarity::EndsWith, values));
444 self
445 }
446
447 pub fn between(&mut self, key: String, values: Vec<String>) -> &mut Self {
448 self.0
449 .insert(key, Parameter::init(Similarity::Between, values));
450 self
451 }
452
453 pub fn lesser(&mut self, key: String, values: Vec<String>) -> &mut Self {
454 self.0
455 .insert(key, Parameter::init(Similarity::Lesser, values));
456 self
457 }
458
459 pub fn lesser_or_equal(&mut self, key: String, values: Vec<String>) -> &mut Self {
460 self.0
461 .insert(key, Parameter::init(Similarity::LesserOrEqual, values));
462 self
463 }
464
465 pub fn greater(&mut self, key: String, values: Vec<String>) -> &mut Self {
466 self.0
467 .insert(key, Parameter::init(Similarity::Greater, values));
468 self
469 }
470
471 pub fn greater_or_equal(&mut self, key: String, values: Vec<String>) -> &mut Self {
472 self.0
473 .insert(key, Parameter::init(Similarity::GreaterOrEqual, values));
474 self
475 }
476
477 pub fn keep(&self, keys: Vec<String>) -> Self {
478 let mut result = Self::new();
479 for key in keys {
480 if let Some(value) = self.0.get(&key) {
481 result.0.insert(key, value.clone());
482 }
483 }
484 result
485 }
486
487 pub fn remove(&self, keys: Vec<String>) -> Self {
488 let mut result = self.clone();
489 for key in keys {
490 result.0.shift_remove(&key);
491 }
492 result
493 }
494}
495
496impl Default for Parameters {
497 fn default() -> Self {
498 Self::new()
499 }
500}
501
502impl FromStr for Parameters {
503 type Err = Error;
504
505 fn from_str(s: &str) -> Result<Self> {
508 let trimmed = s.trim();
509 if trimmed.is_empty() {
510 return Ok(Parameters::new());
511 }
512
513 let str_parameters: Vec<&str> = trimmed.split(AMPERSAND).collect();
514 let mut parameters: Self = Parameters::new();
515
516 for str_param in str_parameters {
517 let trimmed_param = str_param.trim();
518 if trimmed_param.is_empty() {
519 continue;
520 }
521
522 let mut parts = trimmed_param.splitn(2, EQUAL);
523 let (key, value) = match (parts.next(), parts.next()) {
524 (Some(k), Some(v)) => (k, v),
525 _ => return Err(Error::InvalidParameter(trimmed_param.into())),
526 };
527
528 let trimmed_key = key.trim();
529 if trimmed_key.is_empty() || Parameters::EXCLUDE.contains(&trimmed_key) {
530 continue;
531 }
532
533 let param = value.parse::<Parameter>()?;
534 if param.values().is_empty() {
536 continue;
537 }
538
539 parameters.0.insert(trimmed_key.to_string(), param);
540 }
541
542 Ok(parameters)
543 }
544}
545
546impl fmt::Display for Parameters {
547 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548 let params_str = self
549 .inner()
550 .iter()
551 .filter(|(_, param)| param.values().len() > 0)
552 .map(|(key, param)| format!("{key}{EQUAL}{param}"))
553 .collect::<Vec<String>>()
554 .join(&format!("{AMPERSAND}"));
555 write!(f, "{}", params_str)
556 }
557}
558
559#[derive(Clone, Debug, PartialEq)]
560pub struct Parameter(Similarity, Vec<String>);
561
562impl Parameter {
563 pub fn init(similarity: Similarity, values: Vec<String>) -> Self {
564 Self(similarity, values)
565 }
566
567 pub fn similarity(&self) -> &Similarity {
568 &self.0
569 }
570
571 pub fn values(&self) -> &Vec<String> {
572 &self.1
573 }
574
575 pub fn values_mut(&mut self) -> &mut Vec<String> {
576 &mut self.1
577 }
578}
579
580impl FromStr for Parameter {
581 type Err = Error;
582
583 fn from_str(s: &str) -> Result<Self> {
590 let trimmed = s.trim();
591 if trimmed.is_empty() {
592 return Err(Error::InvalidParameter(s.into()));
593 }
594
595 let parts: Vec<&str> = trimmed.split(COLON).collect();
596 if parts.len() != 2 {
597 return Err(Error::InvalidParameter(s.into()));
598 }
599
600 let similarity_str = parts[0].trim();
601 let values_str = parts[1].trim();
602
603 if similarity_str.is_empty() {
604 return Err(Error::InvalidParameter(s.into()));
605 }
606
607 let values: Vec<String> = if values_str.is_empty() {
608 vec![]
609 } else {
610 values_str
611 .split(COMMA)
612 .map(|v| url_decode(v.trim()))
613 .filter(|v| !v.is_empty())
614 .collect()
615 };
616
617 let similarity = similarity_str.parse::<Similarity>()?;
618 Ok(Parameter(similarity, values))
619 }
620}
621
622impl fmt::Display for Parameter {
623 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
624 let similarity_str = self.similarity().to_string();
625 let values_str = self
626 .values()
627 .iter()
628 .map(|v| url_encode(v))
629 .collect::<Vec<String>>()
630 .join(&format!("{COMMA}"));
631 write!(f, "{similarity_str}{COLON}{values_str}")
632 }
633}
634
635#[derive(Clone, Debug, PartialEq)]
636pub struct Order(IndexMap<String, SortDirection>);
637
638impl Order {
639 pub fn new() -> Self {
640 Self(IndexMap::new())
641 }
642
643 pub fn inner(&self) -> &IndexMap<String, SortDirection> {
644 &self.0
645 }
646
647 pub fn inner_mut(&mut self) -> &mut IndexMap<String, SortDirection> {
648 &mut self.0
649 }
650
651 pub fn ascending(&mut self, name: String) -> &mut Self {
652 self.0.insert(name, SortDirection::Ascending);
653 self
654 }
655
656 pub fn descending(&mut self, name: String) -> &mut Self {
657 self.0.insert(name, SortDirection::Descending);
658 self
659 }
660
661 pub fn keep(&self, keys: Vec<String>) -> Self {
662 let mut result = Self::new();
663 for key in keys {
664 if let Some(value) = self.0.get(&key) {
665 result.0.insert(key, value.clone());
666 }
667 }
668 result
669 }
670
671 pub fn remove(&self, keys: Vec<String>) -> Self {
672 let mut result = self.clone();
673 for key in keys {
674 result.0.shift_remove(&key);
675 }
676 result
677 }
678}
679
680impl Default for Order {
681 fn default() -> Self {
682 Self::new()
683 }
684}
685
686impl FromStr for Order {
687 type Err = Error;
688
689 fn from_str(s: &str) -> Result<Self> {
692 let trimmed = s.trim();
693 if trimmed.is_empty() {
694 return Ok(Order::new());
695 }
696
697 let str_fields: Vec<&str> = trimmed.split(COMMA).collect();
698 let mut order: Self = Order::new();
699
700 for str_field in str_fields {
701 let trimmed_field = str_field.trim();
702 if trimmed_field.is_empty() {
703 continue;
704 }
705
706 let OrderField(name, direction) = trimmed_field.parse::<OrderField>()?;
707 order.0.insert(name, direction);
708 }
709
710 Ok(order)
711 }
712}
713
714impl fmt::Display for Order {
715 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
716 let order_str = self
717 .inner()
718 .iter()
719 .filter(|(name, _)| name.len() > 0)
720 .map(|(name, direction)| format!("{}", OrderField(name.clone(), direction.clone())))
721 .collect::<Vec<String>>()
722 .join(&format!("{COMMA}"));
723 write!(f, "{}", order_str)
724 }
725}
726
727pub struct OrderField(String, SortDirection);
728
729impl OrderField {
730 pub fn name(&self) -> &String {
731 &self.0
732 }
733
734 pub fn sort_direction(&self) -> &SortDirection {
735 &self.1
736 }
737}
738
739impl FromStr for OrderField {
740 type Err = Error;
741
742 fn from_str(s: &str) -> Result<Self> {
748 let trimmed = s.trim();
749 if trimmed.is_empty() {
750 return Err(Error::InvalidOrderField(s.into()));
751 }
752
753 let parts: Vec<&str> = trimmed.split(COLON).collect();
754 if parts.len() != 2 {
755 return Err(Error::InvalidOrderField(s.into()));
756 }
757
758 let name = url_decode(parts[0].trim());
759 let order = parts[1].trim();
760
761 if name.is_empty() || order.is_empty() {
762 return Err(Error::InvalidOrderField(s.into()));
763 }
764
765 let order = order.parse::<SortDirection>()?;
766 Ok(OrderField(name, order))
767 }
768}
769
770impl fmt::Display for OrderField {
771 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
772 write!(f, "{}{COLON}{}", self.name(), self.sort_direction())
773 }
774}
775
776#[derive(Clone, Debug, PartialEq)]
778pub enum Similarity {
779 Equals,
780 Contains,
781 StartsWith,
782 EndsWith,
783
784 Between,
785 Lesser,
786 LesserOrEqual,
787 Greater,
788 GreaterOrEqual,
789}
790
791impl Similarity {
792 pub const EQUALS: &str = "equals";
793 pub const CONTAINS: &str = "contains";
794 pub const STARTS_WITH: &str = "starts-with";
795 pub const ENDS_WITH: &str = "ends-with";
796
797 pub const BETWEEN: &str = "between";
798 pub const LESSER: &str = "lesser";
799 pub const LESSER_OR_EQUAL: &str = "lesser-or-equal";
800 pub const GREATER: &str = "greater";
801 pub const GREATER_OR_EQUAL: &str = "greater-or-equal";
802}
803
804impl Default for Similarity {
805 fn default() -> Self {
806 Self::Equals
807 }
808}
809
810impl FromStr for Similarity {
811 type Err = Error;
812 fn from_str(s: &str) -> Result<Self> {
813 match s {
814 Similarity::EQUALS => Ok(Similarity::Equals),
815 Similarity::CONTAINS => Ok(Similarity::Contains),
816 Similarity::STARTS_WITH => Ok(Similarity::StartsWith),
817 Similarity::ENDS_WITH => Ok(Similarity::EndsWith),
818
819 Similarity::BETWEEN => Ok(Similarity::Between),
820 Similarity::LESSER => Ok(Similarity::Lesser),
821 Similarity::LESSER_OR_EQUAL => Ok(Similarity::LesserOrEqual),
822 Similarity::GREATER => Ok(Similarity::Greater),
823 Similarity::GREATER_OR_EQUAL => Ok(Similarity::GreaterOrEqual),
824
825 val => Err(Error::InvalidSimilarity(val.into())),
826 }
827 }
828}
829
830impl fmt::Display for Similarity {
831 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
832 let s = match self {
833 Self::Equals => Self::EQUALS,
834 Self::Contains => Self::CONTAINS,
835 Self::StartsWith => Self::STARTS_WITH,
836 Self::EndsWith => Self::ENDS_WITH,
837
838 Self::Between => Self::BETWEEN,
839 Self::Lesser => Self::LESSER,
840 Self::LesserOrEqual => Self::LESSER_OR_EQUAL,
841 Self::Greater => Self::GREATER,
842 Self::GreaterOrEqual => Self::GREATER_OR_EQUAL,
843 };
844 write!(f, "{}", s)
845 }
846}
847
848#[derive(Clone, Debug, PartialEq)]
849pub enum SortDirection {
850 Ascending,
851 Descending,
852}
853
854impl SortDirection {
855 pub const ASCENDING: &str = "asc";
856 pub const DESCENDING: &str = "desc";
857}
858
859impl Default for SortDirection {
860 fn default() -> Self {
861 Self::Ascending
862 }
863}
864
865impl FromStr for SortDirection {
866 type Err = Error;
867 fn from_str(s: &str) -> Result<Self> {
868 match s {
869 SortDirection::ASCENDING => Ok(SortDirection::Ascending),
870 SortDirection::DESCENDING => Ok(SortDirection::Descending),
871 val => Err(Error::InvalidSortDirection(val.into())),
872 }
873 }
874}
875
876impl fmt::Display for SortDirection {
877 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878 let s = match self {
879 Self::Ascending => SortDirection::ASCENDING,
880 Self::Descending => SortDirection::DESCENDING,
881 };
882 write!(f, "{}", s)
883 }
884}
885
886pub(crate) const QUESTION: char = '?';
887pub(crate) const AMPERSAND: char = '&';
888pub(crate) const EQUAL: char = '=';
889pub(crate) const COLON: char = ':';
890pub(crate) const COMMA: char = ',';
891pub(crate) const PERCENT: char = '%';
892
893pub(crate) fn url_decode(input: &str) -> String {
895 if input.contains(PERCENT) {
897 let query_str = format!("key={}", input);
899 form_urlencoded::parse(query_str.as_bytes())
900 .next()
901 .map(|(_, v)| v.to_string())
902 .unwrap_or_else(|| input.to_string())
903 } else {
904 input.to_string()
905 }
906}
907
908pub(crate) fn url_encode(input: &str) -> String {
910 form_urlencoded::byte_serialize(input.as_bytes()).collect()
911}