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 let where_clause = self.where_clause();
633 if !where_clause.is_empty() {
634 sql_parts.push(format!("WHERE {}", where_clause));
635 }
636
637 let order_clause = self.order_clause();
639 if !order_clause.is_empty() {
640 sql_parts.push(format!("ORDER BY {}", order_clause));
641 }
642
643 sql_parts.push(format!("LIMIT ? OFFSET ?"));
645
646 sql_parts.join(" ")
647 }
648
649 #[cfg(feature = "sql")]
650 pub fn where_clause(&self) -> String {
651 let mut conditions = Vec::new();
652
653 for (key, param) in &self.parameters.0 {
654 let similarity = param.similarity();
655 let values = param.values();
656 if values.is_empty() {
657 continue;
658 }
659
660 let condition = match similarity {
661 Similarity::Equals => {
662 if values.len() == 1 {
663 if values[0] == "null" {
664 format!("{} IS ?", key)
665 } else {
666 format!("{} = ?", key)
667 }
668 } else {
669 let placeholders = vec!["?"; values.len()].join(", ");
670 format!("{} IN ({})", key, placeholders)
671 }
672 }
673 Similarity::Contains => {
674 if values.len() == 1 {
675 format!("{} LIKE ?", key)
676 } else {
677 let like_conditions: Vec<String> =
678 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
679 format!("({})", like_conditions.join(" OR "))
680 }
681 }
682 Similarity::StartsWith => {
683 if values.len() == 1 {
684 format!("{} LIKE ?", key)
685 } else {
686 let like_conditions: Vec<String> =
687 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
688 format!("({})", like_conditions.join(" OR "))
689 }
690 }
691 Similarity::EndsWith => {
692 if values.len() == 1 {
693 format!("{} LIKE ?", key)
694 } else {
695 let like_conditions: Vec<String> =
696 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
697 format!("({})", like_conditions.join(" OR "))
698 }
699 }
700 Similarity::Between => {
701 if values.len() >= 2 {
702 let pairs: Vec<&[String]> = values.chunks(2).collect();
704 let between_conditions: Vec<String> = pairs
705 .iter()
706 .map(|pair| {
707 if pair.len() == 2 {
708 format!("{} BETWEEN ? AND ?", key)
709 } else {
710 String::new() }
712 })
713 .filter(|condition| !condition.is_empty())
714 .collect();
715
716 if between_conditions.is_empty() {
717 continue; } else if between_conditions.len() == 1 {
719 between_conditions[0].clone()
720 } else {
721 format!("({})", between_conditions.join(" OR "))
722 }
723 } else {
724 continue; }
726 }
727 Similarity::Lesser => {
728 if values.len() == 1 {
729 format!("{} < ?", key)
730 } else {
731 let conditions: Vec<String> =
732 values.iter().map(|_| format!("{} < ?", key)).collect();
733 format!("({})", conditions.join(" OR "))
734 }
735 }
736 Similarity::LesserOrEqual => {
737 if values.len() == 1 {
738 format!("{} <= ?", key)
739 } else {
740 let conditions: Vec<String> =
741 values.iter().map(|_| format!("{} <= ?", key)).collect();
742 format!("({})", conditions.join(" OR "))
743 }
744 }
745 Similarity::Greater => {
746 if values.len() == 1 {
747 format!("{} > ?", key)
748 } else {
749 let conditions: Vec<String> =
750 values.iter().map(|_| format!("{} > ?", key)).collect();
751 format!("({})", conditions.join(" OR "))
752 }
753 }
754 Similarity::GreaterOrEqual => {
755 if values.len() == 1 {
756 format!("{} >= ?", key)
757 } else {
758 let conditions: Vec<String> =
759 values.iter().map(|_| format!("{} >= ?", key)).collect();
760 format!("({})", conditions.join(" OR "))
761 }
762 }
763 };
764
765 conditions.push(condition);
766 }
767
768 conditions.join(" AND ")
769 }
770
771 #[cfg(feature = "sql")]
772 pub fn order_clause(&self) -> String {
773 let mut order_parts = Vec::new();
774
775 for (name, order) in &self.sort_fields.0 {
776 if !name.is_empty() {
777 let direction = match order {
778 SortOrder::Ascending => "ASC",
779 SortOrder::Descending => "DESC",
780 };
781 order_parts.push(format!("{} {}", name, direction));
782 }
783 }
784
785 order_parts.join(", ")
786 }
787
788 #[cfg(feature = "sql")]
789 pub fn to_values(&self) -> Vec<SqlValue> {
790 let mut sql_values = self.parameter_values();
791 sql_values.extend(self.pagination_values());
792 sql_values
793 }
794
795 #[cfg(feature = "sql")]
796 pub fn parameter_values(&self) -> Vec<SqlValue> {
798 let mut sql_values = Vec::new();
799
800 for (_k, param) in self.parameters.inner() {
801 let param_similarity = param.similarity();
802 let param_values = param.values();
803 for cur_val in param_values {
804 if cur_val.trim().is_empty() {
806 continue;
807 }
808
809 if cur_val == "null" {
810 sql_values.push(SqlValue::Null);
811 continue;
812 }
813
814 let sql_value = match *param_similarity {
815 Similarity::Contains => SqlValue::Text(format!("%{}%", cur_val)),
816 Similarity::StartsWith => SqlValue::Text(format!("{}%", cur_val)),
817 Similarity::EndsWith => SqlValue::Text(format!("%{}", cur_val)),
818 _ => {
819 if let Ok(i) = cur_val.parse::<i64>() {
821 SqlValue::Integer(i)
822 } else if let Ok(f) = cur_val.parse::<f64>() {
823 SqlValue::Real(f)
824 } else {
825 SqlValue::Text(cur_val.clone())
826 }
827 }
828 };
829
830 sql_values.push(sql_value);
831 }
832 }
833
834 sql_values
835 }
836
837 #[cfg(feature = "sql")]
838 pub fn pagination_values(&self) -> Vec<SqlValue> {
840 vec![
841 SqlValue::Integer(self.limit as i64),
842 SqlValue::Integer(self.offset as i64),
843 ]
844 }
845
846 #[cfg(feature = "sql")]
847 pub fn total_parameters(&self) -> usize {
850 let parameter_count: usize = self
851 .parameters
852 .inner()
853 .values()
854 .map(|param| {
855 param
856 .values()
857 .iter()
858 .filter(|v| !v.trim().is_empty())
859 .count()
860 })
861 .sum();
862
863 parameter_count + 2 }
865}