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 asc(&mut self, name: String) -> &mut Self {
146 self.0.insert(name, SortOrder::Ascending);
147 self
148 }
149
150 pub fn desc(&mut self, name: String) -> &mut Self {
151 self.0.insert(name, SortOrder::Descending);
152 self
153 }
154
155 pub fn keep(&self, keys: Vec<String>) -> Self {
156 let mut result = Self::new();
157 for key in keys {
158 if let Some(value) = self.0.get(&key) {
159 result.0.insert(key, value.clone());
160 }
161 }
162 result
163 }
164
165 pub fn remove(&self, keys: Vec<String>) -> Self {
166 let mut result = self.clone();
167 for key in keys {
168 result.0.shift_remove(&key);
169 }
170 result
171 }
172}
173
174impl Default for SortFields {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180impl FromStr for SortFields {
181 type Err = Error;
182
183 fn from_str(s: &str) -> Result<Self> {
186 let trimmed = s.trim();
187 if trimmed.is_empty() {
188 return Ok(SortFields::new());
189 }
190
191 let str_fields: Vec<&str> = trimmed.split(COMMA).collect();
192 let mut sort_fields: Self = SortFields(IndexMap::new());
193
194 for str_field in str_fields {
195 let trimmed_field = str_field.trim();
196 if trimmed_field.is_empty() {
197 continue;
198 }
199
200 let (name, order) = parse_sort_field(trimmed_field)?;
201 sort_fields.0.insert(name, order);
202 }
203
204 Ok(sort_fields)
205 }
206}
207
208#[derive(Clone, Debug, PartialEq)]
209pub enum Similarity {
210 Equals,
211 Contains,
212 StartsWith,
213 EndsWith,
214
215 Between,
216 Lesser,
217 LesserOrEqual,
218 Greater,
219 GreaterOrEqual,
220}
221
222impl Similarity {
223 pub const EQUALS: &str = "equals";
224 pub const CONTAINS: &str = "contains";
225 pub const STARTS_WITH: &str = "starts-with";
226 pub const ENDS_WITH: &str = "ends-with";
227
228 pub const BETWEEN: &str = "between";
229 pub const LESSER: &str = "lesser";
230 pub const LESSER_OR_EQUAL: &str = "lesser-or-equal";
231 pub const GREATER: &str = "greater";
232 pub const GREATER_OR_EQUAL: &str = "greater-or-equal";
233}
234
235impl Default for Similarity {
236 fn default() -> Self {
237 Self::Equals
238 }
239}
240
241impl FromStr for Similarity {
242 type Err = Error;
243 fn from_str(s: &str) -> Result<Self> {
244 match s {
245 Similarity::EQUALS => Ok(Similarity::Equals),
246 Similarity::CONTAINS => Ok(Similarity::Contains),
247 Similarity::STARTS_WITH => Ok(Similarity::StartsWith),
248 Similarity::ENDS_WITH => Ok(Similarity::EndsWith),
249
250 Similarity::BETWEEN => Ok(Similarity::Between),
251 Similarity::LESSER => Ok(Similarity::Lesser),
252 Similarity::LESSER_OR_EQUAL => Ok(Similarity::LesserOrEqual),
253 Similarity::GREATER => Ok(Similarity::Greater),
254 Similarity::GREATER_OR_EQUAL => Ok(Similarity::GreaterOrEqual),
255
256 val => Err(Error::InvalidSimilarity(val.into())),
257 }
258 }
259}
260
261impl ToString for Similarity {
262 fn to_string(&self) -> String {
263 match self {
264 Self::Equals => Self::EQUALS.to_string(),
265 Self::Contains => Self::CONTAINS.to_string(),
266 Self::StartsWith => Self::STARTS_WITH.to_string(),
267 Self::EndsWith => Self::ENDS_WITH.to_string(),
268
269 Self::Between => Self::BETWEEN.to_string(),
270 Self::Lesser => Self::LESSER.to_string(),
271 Self::LesserOrEqual => Self::LESSER_OR_EQUAL.to_string(),
272 Self::Greater => Self::GREATER.to_string(),
273 Self::GreaterOrEqual => Self::GREATER_OR_EQUAL.to_string(),
274 }
275 }
276}
277
278#[derive(Clone, Debug, PartialEq)]
279pub struct Parameters(pub IndexMap<String, (Similarity, Vec<String>)>);
280
281impl Parameters {
282 pub const ORDER: &str = "order";
283 pub const LIMIT: &str = "limit";
284 pub const OFFSET: &str = "offset";
285
286 pub const EXCLUDE: [&str; 3] = [Parameters::ORDER, Parameters::LIMIT, Parameters::OFFSET];
287
288 pub const DEFAULT_LIMIT: usize = 50;
289 pub const DEFAULT_OFFSET: usize = 0;
290
291 pub fn new() -> Self {
292 Self(IndexMap::new())
293 }
294
295 pub fn equals(&mut self, key: String, values: Vec<String>) -> &mut Self {
296 self.0.insert(key, (Similarity::Equals, values));
297 self
298 }
299
300 pub fn contains(&mut self, key: String, values: Vec<String>) -> &mut Self {
301 self.0.insert(key, (Similarity::Contains, values));
302 self
303 }
304
305 pub fn starts_with(&mut self, key: String, values: Vec<String>) -> &mut Self {
306 self.0.insert(key, (Similarity::StartsWith, values));
307 self
308 }
309
310 pub fn ends_with(&mut self, key: String, values: Vec<String>) -> &mut Self {
311 self.0.insert(key, (Similarity::EndsWith, values));
312 self
313 }
314
315 pub fn between(&mut self, key: String, values: Vec<String>) -> &mut Self {
316 self.0.insert(key, (Similarity::Between, values));
317 self
318 }
319
320 pub fn lesser(&mut self, key: String, values: Vec<String>) -> &mut Self {
321 self.0.insert(key, (Similarity::Lesser, values));
322 self
323 }
324
325 pub fn lesser_or_equal(&mut self, key: String, values: Vec<String>) -> &mut Self {
326 self.0.insert(key, (Similarity::LesserOrEqual, values));
327 self
328 }
329
330 pub fn greater(&mut self, key: String, values: Vec<String>) -> &mut Self {
331 self.0.insert(key, (Similarity::Greater, values));
332 self
333 }
334
335 pub fn greater_or_equal(&mut self, key: String, values: Vec<String>) -> &mut Self {
336 self.0.insert(key, (Similarity::GreaterOrEqual, values));
337 self
338 }
339
340 pub fn keep(&self, keys: Vec<String>) -> Self {
341 let mut result = Self::new();
342 for key in keys {
343 if let Some(value) = self.0.get(&key) {
344 result.0.insert(key, value.clone());
345 }
346 }
347 result
348 }
349
350 pub fn remove(&self, keys: Vec<String>) -> Self {
351 let mut result = self.clone();
352 for key in keys {
353 result.0.shift_remove(&key);
354 }
355 result
356 }
357}
358
359impl Default for Parameters {
360 fn default() -> Self {
361 Self::new()
362 }
363}
364
365impl FromStr for Parameters {
366 type Err = Error;
367
368 fn from_str(s: &str) -> Result<Self> {
371 let trimmed = s.trim();
372 if trimmed.is_empty() {
373 return Ok(Parameters::new());
374 }
375
376 let str_parameters: Vec<&str> = trimmed.split(AMPERSAND).collect();
377 let mut parameters: Self = Parameters(IndexMap::new());
378
379 for str_param in str_parameters {
380 let trimmed_param = str_param.trim();
381 if trimmed_param.is_empty() {
382 continue;
383 }
384
385 let mut parts = trimmed_param.splitn(2, EQUAL);
386 let (key, value) = match (parts.next(), parts.next()) {
387 (Some(k), Some(v)) => (k, v),
388 _ => return Err(Error::InvalidParameter(trimmed_param.into())),
389 };
390
391 let trimmed_key = key.trim();
392 if trimmed_key.is_empty() || Parameters::EXCLUDE.contains(&trimmed_key) {
393 continue;
394 }
395
396 let (similarity, values) = parse_parameter(value)?;
397 if values.is_empty() {
399 continue;
400 }
401
402 parameters
403 .0
404 .insert(trimmed_key.to_string(), (similarity, values));
405 }
406
407 Ok(parameters)
408 }
409}
410
411#[derive(Clone, Debug, PartialEq)]
412pub struct Query {
413 pub parameters: Parameters,
414 pub sort_fields: SortFields,
415 pub limit: usize,
416 pub offset: usize,
417}
418
419impl Query {
420 pub fn new() -> Self {
421 Self {
422 parameters: Parameters::new(),
423 sort_fields: SortFields::new(),
424 limit: Parameters::DEFAULT_LIMIT,
425 offset: Parameters::DEFAULT_OFFSET,
426 }
427 }
428
429 pub fn init(
430 parameters: Parameters,
431 sort_fields: SortFields,
432 limit: usize,
433 offset: usize,
434 ) -> Self {
435 Self {
436 parameters,
437 sort_fields,
438 limit,
439 offset,
440 }
441 }
442
443 pub fn to_http(&self) -> String {
444 let mut params = self
445 .parameters
446 .0
447 .iter()
448 .filter(|(_, (_, values))| values.len() > 0)
449 .map(|(key, (similarity, values))| {
450 let similarity_str = similarity.to_string();
451 let values_str = values
452 .iter()
453 .map(|v| url_encode(v))
454 .collect::<Vec<String>>()
455 .join(&format!("{COMMA}"));
456 format!("{key}{EQUAL}{similarity_str}{COLON}{values_str}",)
457 })
458 .collect::<Vec<String>>()
459 .join("&");
460
461 let order = self
462 .sort_fields
463 .0
464 .iter()
465 .filter(|(name, _)| name.len() > 0)
466 .map(|(name, order)| format!("{name}{COLON}{}", order.to_string()))
467 .collect::<Vec<String>>()
468 .join(&format!("{COMMA}"));
469
470 if params.len() > 0 {
471 params.push_str(&format!("{AMPERSAND}"));
472 }
473
474 if order.len() > 0 {
475 params.push_str(&order);
476 params.push_str(&format!("{AMPERSAND}"));
477 }
478
479 format!(
480 "{params}{}{EQUAL}{}{AMPERSAND}{}{EQUAL}{}",
481 Parameters::LIMIT,
482 self.limit,
483 Parameters::OFFSET,
484 self.offset,
485 )
486 }
487
488 pub fn from_http(search: String) -> Result<Self> {
490 let mut query = Self::new();
491 let trimmed_search = search.trim_start_matches(QUESTION).trim();
492
493 if trimmed_search.is_empty() {
494 return Ok(query);
495 }
496
497 for k_v in trimmed_search.split(AMPERSAND) {
498 let trimmed_kv = k_v.trim();
499 if trimmed_kv.is_empty() {
500 continue;
501 }
502
503 let mut parts = trimmed_kv.splitn(2, EQUAL);
504 if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
505 let trimmed_key = key.trim();
506 let trimmed_value = value.trim();
507
508 if trimmed_key.is_empty() {
509 continue;
510 }
511
512 match trimmed_key {
513 Parameters::ORDER => {
514 if trimmed_value.is_empty() {
515 continue;
516 }
517
518 if !trimmed_value.contains(COLON) {
520 return Err(Error::InvalidSortField(trimmed_value.into()));
522 }
523
524 if let Ok(sort_fields) = SortFields::from_str(trimmed_value) {
525 query.sort_fields = sort_fields;
526 }
527 }
529 Parameters::LIMIT => {
530 if trimmed_value.is_empty() {
531 continue;
532 }
533
534 query.limit = trimmed_value.parse().unwrap_or(Parameters::DEFAULT_LIMIT);
535 }
536 Parameters::OFFSET => {
537 if trimmed_value.is_empty() {
538 continue;
539 }
540
541 query.offset = trimmed_value.parse().unwrap_or(Parameters::DEFAULT_OFFSET);
542 }
543 _k => {
544 if trimmed_value.is_empty() {
545 continue;
546 }
547
548 if trimmed_value.contains(COLON) {
550 let (similarity, values) = parse_parameter(trimmed_value)?;
552 if values.is_empty() {
554 continue;
555 }
556 query
558 .parameters
559 .0
560 .insert(trimmed_key.to_string(), (similarity, values));
561 } else {
562 let decoded_value = url_decode(trimmed_value);
564
565 if let Some((existing_similarity, existing_values)) =
567 query.parameters.0.get_mut(&trimmed_key.to_string())
568 {
569 if *existing_similarity == Similarity::Equals {
571 existing_values.push(decoded_value);
572 }
573 } else {
575 query.parameters.0.insert(
577 trimmed_key.to_string(),
578 (Similarity::Equals, vec![decoded_value]),
579 );
580 }
581 }
582 }
583 }
584 } else {
585 return Err(Error::InvalidSearchParameters(search));
586 }
587 }
588
589 Ok(query)
590 }
591
592 #[cfg(feature = "sql")]
593 pub fn to_sql(&self) -> String {
594 let mut sql_parts = Vec::new();
595
596 let where_clause = self.build_where_clause();
598 if !where_clause.is_empty() {
599 sql_parts.push(format!("WHERE {}", where_clause));
600 }
601
602 let order_clause = self.build_order_clause();
604 if !order_clause.is_empty() {
605 sql_parts.push(format!("ORDER BY {}", order_clause));
606 }
607
608 sql_parts.push(format!("LIMIT ? OFFSET ?"));
610
611 sql_parts.join(" ")
612 }
613
614 #[cfg(feature = "sql")]
615 fn build_where_clause(&self) -> String {
616 let mut conditions = Vec::new();
617
618 for (key, (similarity, values)) in &self.parameters.0 {
619 if values.is_empty() {
620 continue;
621 }
622
623 let condition = match similarity {
624 Similarity::Equals => {
625 if values.len() == 1 {
626 if values[0] == "null" {
627 format!("{} IS ?", key)
628 } else {
629 format!("{} = ?", key)
630 }
631 } else {
632 let placeholders = vec!["?"; values.len()].join(", ");
633 format!("{} IN ({})", key, placeholders)
634 }
635 }
636 Similarity::Contains => {
637 if values.len() == 1 {
638 format!("{} LIKE ?", key)
639 } else {
640 let like_conditions: Vec<String> =
641 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
642 format!("({})", like_conditions.join(" OR "))
643 }
644 }
645 Similarity::StartsWith => {
646 if values.len() == 1 {
647 format!("{} LIKE ?", key)
648 } else {
649 let like_conditions: Vec<String> =
650 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
651 format!("({})", like_conditions.join(" OR "))
652 }
653 }
654 Similarity::EndsWith => {
655 if values.len() == 1 {
656 format!("{} LIKE ?", key)
657 } else {
658 let like_conditions: Vec<String> =
659 values.iter().map(|_| format!("{} LIKE ?", key)).collect();
660 format!("({})", like_conditions.join(" OR "))
661 }
662 }
663 Similarity::Between => {
664 if values.len() >= 2 {
665 let pairs: Vec<&[String]> = values.chunks(2).collect();
667 let between_conditions: Vec<String> = pairs
668 .iter()
669 .map(|pair| {
670 if pair.len() == 2 {
671 format!("{} BETWEEN ? AND ?", key)
672 } else {
673 String::new() }
675 })
676 .filter(|condition| !condition.is_empty())
677 .collect();
678
679 if between_conditions.is_empty() {
680 continue; } else if between_conditions.len() == 1 {
682 between_conditions[0].clone()
683 } else {
684 format!("({})", between_conditions.join(" OR "))
685 }
686 } else {
687 continue; }
689 }
690 Similarity::Lesser => {
691 if values.len() == 1 {
692 format!("{} < ?", key)
693 } else {
694 let conditions: Vec<String> =
695 values.iter().map(|_| format!("{} < ?", key)).collect();
696 format!("({})", conditions.join(" OR "))
697 }
698 }
699 Similarity::LesserOrEqual => {
700 if values.len() == 1 {
701 format!("{} <= ?", key)
702 } else {
703 let conditions: Vec<String> =
704 values.iter().map(|_| format!("{} <= ?", key)).collect();
705 format!("({})", conditions.join(" OR "))
706 }
707 }
708 Similarity::Greater => {
709 if values.len() == 1 {
710 format!("{} > ?", key)
711 } else {
712 let conditions: Vec<String> =
713 values.iter().map(|_| format!("{} > ?", key)).collect();
714 format!("({})", conditions.join(" OR "))
715 }
716 }
717 Similarity::GreaterOrEqual => {
718 if values.len() == 1 {
719 format!("{} >= ?", key)
720 } else {
721 let conditions: Vec<String> =
722 values.iter().map(|_| format!("{} >= ?", key)).collect();
723 format!("({})", conditions.join(" OR "))
724 }
725 }
726 };
727
728 conditions.push(condition);
729 }
730
731 conditions.join(" AND ")
732 }
733
734 #[cfg(feature = "sql")]
735 fn build_order_clause(&self) -> String {
736 let mut order_parts = Vec::new();
737
738 for (name, order) in &self.sort_fields.0 {
739 if !name.is_empty() {
740 let direction = match order {
741 SortOrder::Ascending => "ASC",
742 SortOrder::Descending => "DESC",
743 };
744 order_parts.push(format!("{} {}", name, direction));
745 }
746 }
747
748 order_parts.join(", ")
749 }
750}