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