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