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<(Similarity, Vec<String>)> {
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((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
286pub type Parameter = (Similarity, Vec<String>);
287pub trait ParameterGet {
288 fn similarity(&self) -> &Similarity;
289 fn values(&self) -> &Vec<String>;
290}
291
292impl ParameterGet for Parameter {
293 fn similarity(&self) -> &Similarity {
294 &self.0
295 }
296
297 fn values(&self) -> &Vec<String> {
298 &self.1
299 }
300}
301
302#[derive(Clone, Debug, PartialEq)]
303pub struct Parameters(pub IndexMap<String, Parameter>);
304
305impl Parameters {
306 pub const ORDER: &str = "order";
307 pub const LIMIT: &str = "limit";
308 pub const OFFSET: &str = "offset";
309
310 pub const EXCLUDE: [&str; 3] = [Parameters::ORDER, Parameters::LIMIT, Parameters::OFFSET];
311
312 pub const DEFAULT_LIMIT: usize = 50;
313 pub const DEFAULT_OFFSET: usize = 0;
314
315 pub fn new() -> Self {
316 Self(IndexMap::new())
317 }
318
319 pub fn inner(&self) -> &IndexMap<String, Parameter> {
320 &self.0
321 }
322
323 pub fn inner_mut(&mut self) -> &mut IndexMap<String, Parameter> {
324 &mut self.0
325 }
326
327 pub fn equals(&mut self, key: String, values: Vec<String>) -> &mut Self {
328 self.0.insert(key, (Similarity::Equals, values));
329 self
330 }
331
332 pub fn contains(&mut self, key: String, values: Vec<String>) -> &mut Self {
333 self.0.insert(key, (Similarity::Contains, values));
334 self
335 }
336
337 pub fn starts_with(&mut self, key: String, values: Vec<String>) -> &mut Self {
338 self.0.insert(key, (Similarity::StartsWith, values));
339 self
340 }
341
342 pub fn ends_with(&mut self, key: String, values: Vec<String>) -> &mut Self {
343 self.0.insert(key, (Similarity::EndsWith, values));
344 self
345 }
346
347 pub fn between(&mut self, key: String, values: Vec<String>) -> &mut Self {
348 self.0.insert(key, (Similarity::Between, values));
349 self
350 }
351
352 pub fn lesser(&mut self, key: String, values: Vec<String>) -> &mut Self {
353 self.0.insert(key, (Similarity::Lesser, values));
354 self
355 }
356
357 pub fn lesser_or_equal(&mut self, key: String, values: Vec<String>) -> &mut Self {
358 self.0.insert(key, (Similarity::LesserOrEqual, values));
359 self
360 }
361
362 pub fn greater(&mut self, key: String, values: Vec<String>) -> &mut Self {
363 self.0.insert(key, (Similarity::Greater, values));
364 self
365 }
366
367 pub fn greater_or_equal(&mut self, key: String, values: Vec<String>) -> &mut Self {
368 self.0.insert(key, (Similarity::GreaterOrEqual, values));
369 self
370 }
371
372 pub fn keep(&self, keys: Vec<String>) -> Self {
373 let mut result = Self::new();
374 for key in keys {
375 if let Some(value) = self.0.get(&key) {
376 result.0.insert(key, value.clone());
377 }
378 }
379 result
380 }
381
382 pub fn remove(&self, keys: Vec<String>) -> Self {
383 let mut result = self.clone();
384 for key in keys {
385 result.0.shift_remove(&key);
386 }
387 result
388 }
389}
390
391impl Default for Parameters {
392 fn default() -> Self {
393 Self::new()
394 }
395}
396
397impl FromStr for Parameters {
398 type Err = Error;
399
400 fn from_str(s: &str) -> Result<Self> {
403 let trimmed = s.trim();
404 if trimmed.is_empty() {
405 return Ok(Parameters::new());
406 }
407
408 let str_parameters: Vec<&str> = trimmed.split(AMPERSAND).collect();
409 let mut parameters: Self = Parameters(IndexMap::new());
410
411 for str_param in str_parameters {
412 let trimmed_param = str_param.trim();
413 if trimmed_param.is_empty() {
414 continue;
415 }
416
417 let mut parts = trimmed_param.splitn(2, EQUAL);
418 let (key, value) = match (parts.next(), parts.next()) {
419 (Some(k), Some(v)) => (k, v),
420 _ => return Err(Error::InvalidParameter(trimmed_param.into())),
421 };
422
423 let trimmed_key = key.trim();
424 if trimmed_key.is_empty() || Parameters::EXCLUDE.contains(&trimmed_key) {
425 continue;
426 }
427
428 let (similarity, values) = parse_parameter(value)?;
429 if values.is_empty() {
431 continue;
432 }
433
434 parameters
435 .0
436 .insert(trimmed_key.to_string(), (similarity, values));
437 }
438
439 Ok(parameters)
440 }
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub struct Query {
445 pub parameters: Parameters,
446 pub sort_fields: SortFields,
447 pub limit: usize,
448 pub offset: usize,
449}
450
451impl Query {
452 pub fn new() -> Self {
453 Self {
454 parameters: Parameters::new(),
455 sort_fields: SortFields::new(),
456 limit: Parameters::DEFAULT_LIMIT,
457 offset: Parameters::DEFAULT_OFFSET,
458 }
459 }
460
461 pub fn init(
462 parameters: Parameters,
463 sort_fields: SortFields,
464 limit: usize,
465 offset: usize,
466 ) -> Self {
467 Self {
468 parameters,
469 sort_fields,
470 limit,
471 offset,
472 }
473 }
474
475 pub fn to_http(&self) -> String {
476 let mut params = self
477 .parameters
478 .0
479 .iter()
480 .filter(|(_, (_, values))| values.len() > 0)
481 .map(|(key, (similarity, values))| {
482 let similarity_str = similarity.to_string();
483 let values_str = values
484 .iter()
485 .map(|v| url_encode(v))
486 .collect::<Vec<String>>()
487 .join(&format!("{COMMA}"));
488 format!("{key}{EQUAL}{similarity_str}{COLON}{values_str}",)
489 })
490 .collect::<Vec<String>>()
491 .join("&");
492
493 let order = self
494 .sort_fields
495 .0
496 .iter()
497 .filter(|(name, _)| name.len() > 0)
498 .map(|(name, order)| format!("{name}{COLON}{}", order.to_string()))
499 .collect::<Vec<String>>()
500 .join(&format!("{COMMA}"));
501
502 if params.len() > 0 {
503 params.push_str(&format!("{AMPERSAND}"));
504 }
505
506 if order.len() > 0 {
507 params.push_str(&format!("{}{EQUAL}{}", Parameters::ORDER, order));
508 params.push_str(&format!("{AMPERSAND}"));
509 }
510
511 format!(
512 "{params}{}{EQUAL}{}{AMPERSAND}{}{EQUAL}{}",
513 Parameters::LIMIT,
514 self.limit,
515 Parameters::OFFSET,
516 self.offset,
517 )
518 }
519
520 pub fn from_http(search: String) -> Result<Self> {
522 let mut query = Self::new();
523 let trimmed_search = search.trim_start_matches(QUESTION).trim();
524
525 if trimmed_search.is_empty() {
526 return Ok(query);
527 }
528
529 for k_v in trimmed_search.split(AMPERSAND) {
530 let trimmed_kv = k_v.trim();
531 if trimmed_kv.is_empty() {
532 continue;
533 }
534
535 let mut parts = trimmed_kv.splitn(2, EQUAL);
536 if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
537 let trimmed_key = key.trim();
538 let trimmed_value = value.trim();
539
540 if trimmed_key.is_empty() {
541 continue;
542 }
543
544 match trimmed_key {
545 Parameters::ORDER => {
546 if trimmed_value.is_empty() {
547 continue;
548 }
549
550 if !trimmed_value.contains(COLON) {
552 return Err(Error::InvalidSortField(trimmed_value.into()));
554 }
555
556 if let Ok(sort_fields) = SortFields::from_str(trimmed_value) {
557 query.sort_fields = sort_fields;
558 }
559 }
561 Parameters::LIMIT => {
562 if trimmed_value.is_empty() {
563 continue;
564 }
565
566 query.limit = trimmed_value.parse().unwrap_or(Parameters::DEFAULT_LIMIT);
567 }
568 Parameters::OFFSET => {
569 if trimmed_value.is_empty() {
570 continue;
571 }
572
573 query.offset = trimmed_value.parse().unwrap_or(Parameters::DEFAULT_OFFSET);
574 }
575 _k => {
576 if trimmed_value.is_empty() {
577 continue;
578 }
579
580 if trimmed_value.contains(COLON) {
582 let (similarity, values) = parse_parameter(trimmed_value)?;
584 if values.is_empty() {
586 continue;
587 }
588 query
590 .parameters
591 .0
592 .insert(trimmed_key.to_string(), (similarity, values));
593 } else {
594 let decoded_value = url_decode(trimmed_value);
596
597 if let Some((existing_similarity, existing_values)) =
599 query.parameters.0.get_mut(&trimmed_key.to_string())
600 {
601 if *existing_similarity == Similarity::Equals {
603 existing_values.push(decoded_value);
604 }
605 } else {
607 query.parameters.0.insert(
609 trimmed_key.to_string(),
610 (Similarity::Equals, vec![decoded_value]),
611 );
612 }
613 }
614 }
615 }
616 } else {
617 return Err(Error::InvalidSearchParameters(search));
618 }
619 }
620
621 Ok(query)
622 }
623
624 #[cfg(feature = "sql")]
625 pub fn to_sql(&self) -> String {
626 let mut sql_parts = Vec::new();
627
628 let where_clause = self.build_where_clause();
630 if !where_clause.is_empty() {
631 sql_parts.push(format!("WHERE {}", where_clause));
632 }
633
634 let order_clause = self.build_order_clause();
636 if !order_clause.is_empty() {
637 sql_parts.push(format!("ORDER BY {}", order_clause));
638 }
639
640 sql_parts.push(format!("LIMIT ? OFFSET ?"));
642
643 sql_parts.join(" ")
644 }
645
646 #[cfg(feature = "sql")]
647 fn build_where_clause(&self) -> String {
648 let mut conditions = Vec::new();
649
650 for (key, (similarity, values)) in &self.parameters.0 {
651 if values.is_empty() {
652 continue;
653 }
654
655 let condition = match similarity {
656 Similarity::Equals => {
657 if values.len() == 1 {
658 if values[0] == "null" {
659 format!("{} IS ?", key)
660 } else {
661 format!("{} = ?", key)
662 }
663 } else {
664 let placeholders = vec!["?"; values.len()].join(", ");
665 format!("{} IN ({})", key, placeholders)
666 }
667 }
668 Similarity::Contains => {
669 if values.len() == 1 {
670 format!("{} LIKE ?", key)
671 } else {
672 let like_conditions: Vec<String> =
673 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
674 format!("({})", like_conditions.join(" OR "))
675 }
676 }
677 Similarity::StartsWith => {
678 if values.len() == 1 {
679 format!("{} LIKE ?", key)
680 } else {
681 let like_conditions: Vec<String> =
682 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
683 format!("({})", like_conditions.join(" OR "))
684 }
685 }
686 Similarity::EndsWith => {
687 if values.len() == 1 {
688 format!("{} LIKE ?", key)
689 } else {
690 let like_conditions: Vec<String> =
691 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
692 format!("({})", like_conditions.join(" OR "))
693 }
694 }
695 Similarity::Between => {
696 if values.len() >= 2 {
697 let pairs: Vec<&[String]> = values.chunks(2).collect();
699 let between_conditions: Vec<String> = pairs
700 .iter()
701 .map(|pair| {
702 if pair.len() == 2 {
703 format!("{} BETWEEN ? AND ?", key)
704 } else {
705 String::new() }
707 })
708 .filter(|condition| !condition.is_empty())
709 .collect();
710
711 if between_conditions.is_empty() {
712 continue; } else if between_conditions.len() == 1 {
714 between_conditions[0].clone()
715 } else {
716 format!("({})", between_conditions.join(" OR "))
717 }
718 } else {
719 continue; }
721 }
722 Similarity::Lesser => {
723 if values.len() == 1 {
724 format!("{} < ?", key)
725 } else {
726 let conditions: Vec<String> =
727 values.iter().map(|_| format!("{} < ?", key)).collect();
728 format!("({})", conditions.join(" OR "))
729 }
730 }
731 Similarity::LesserOrEqual => {
732 if values.len() == 1 {
733 format!("{} <= ?", key)
734 } else {
735 let conditions: Vec<String> =
736 values.iter().map(|_| format!("{} <= ?", key)).collect();
737 format!("({})", conditions.join(" OR "))
738 }
739 }
740 Similarity::Greater => {
741 if values.len() == 1 {
742 format!("{} > ?", key)
743 } else {
744 let conditions: Vec<String> =
745 values.iter().map(|_| format!("{} > ?", key)).collect();
746 format!("({})", conditions.join(" OR "))
747 }
748 }
749 Similarity::GreaterOrEqual => {
750 if values.len() == 1 {
751 format!("{} >= ?", key)
752 } else {
753 let conditions: Vec<String> =
754 values.iter().map(|_| format!("{} >= ?", key)).collect();
755 format!("({})", conditions.join(" OR "))
756 }
757 }
758 };
759
760 conditions.push(condition);
761 }
762
763 conditions.join(" AND ")
764 }
765
766 #[cfg(feature = "sql")]
767 fn build_order_clause(&self) -> String {
768 let mut order_parts = Vec::new();
769
770 for (name, order) in &self.sort_fields.0 {
771 if !name.is_empty() {
772 let direction = match order {
773 SortOrder::Ascending => "ASC",
774 SortOrder::Descending => "DESC",
775 };
776 order_parts.push(format!("{} {}", name, direction));
777 }
778 }
779
780 order_parts.join(", ")
781 }
782}