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