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
35#[derive(Clone, Debug, PartialEq)]
36pub enum SortOrder {
37 Ascending,
38 Descending,
39}
40
41impl SortOrder {
42 pub const ASCENDING: &str = "asc";
43 pub const DESCENDING: &str = "desc";
44}
45
46impl Default for SortOrder {
47 fn default() -> Self {
48 Self::Ascending
49 }
50}
51
52impl FromStr for SortOrder {
53 type Err = Error;
54 fn from_str(s: &str) -> Result<Self> {
55 match s {
56 SortOrder::ASCENDING => Ok(SortOrder::Ascending),
57 SortOrder::DESCENDING => Ok(SortOrder::Descending),
58 val => Err(Error::InvalidSortOrder(val.into())),
59 }
60 }
61}
62
63impl ToString for SortOrder {
64 fn to_string(&self) -> String {
65 match self {
66 Self::Ascending => SortOrder::ASCENDING.to_string(),
67 Self::Descending => SortOrder::DESCENDING.to_string(),
68 }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq)]
73pub struct SortField {
74 pub name: String,
75 pub order: SortOrder,
76}
77
78impl SortField {
79 pub fn init(name: String, order: SortOrder) -> Self {
80 Self { name, order }
81 }
82}
83
84impl FromStr for SortField {
85 type Err = Error;
86
87 fn from_str(s: &str) -> Result<Self> {
92 let trimmed = s.trim();
93 if trimmed.is_empty() {
94 return Err(Error::InvalidSortField(s.into()));
95 }
96
97 let parts: Vec<&str> = trimmed.split(COLON).collect();
98 if parts.len() != 2 {
99 return Err(Error::InvalidSortField(s.into()));
100 }
101
102 let name = url_decode(parts[0].trim());
103 let order_str = parts[1].trim();
104
105 if name.is_empty() || order_str.is_empty() {
106 return Err(Error::InvalidSortField(s.into()));
107 }
108
109 Ok(SortField::init(name, SortOrder::from_str(order_str)?))
110 }
111}
112
113#[derive(Clone, Debug, PartialEq)]
114pub struct SortFields(pub Vec<SortField>);
115
116impl SortFields {
117 pub fn new() -> Self {
118 Self(Vec::new())
119 }
120}
121
122impl Default for SortFields {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl FromStr for SortFields {
129 type Err = Error;
130
131 fn from_str(s: &str) -> Result<Self> {
134 let trimmed = s.trim();
135 if trimmed.is_empty() {
136 return Ok(SortFields::new());
137 }
138
139 let str_fields: Vec<&str> = trimmed.split(COMMA).collect();
140 let mut sort_fields: Self = SortFields(vec![]);
141
142 for str_field in str_fields {
143 let trimmed_field = str_field.trim();
144 if trimmed_field.is_empty() {
145 continue;
146 }
147
148 sort_fields.0.push(SortField::from_str(trimmed_field)?);
149 }
150
151 Ok(sort_fields)
152 }
153}
154
155#[derive(Clone, Debug, PartialEq)]
156pub enum Similarity {
157 Equals,
158 Contains,
159 StartsWith,
160 EndsWith,
161
162 Between,
163 Lesser,
164 LesserOrEqual,
165 Greater,
166 GreaterOrEqual,
167}
168
169impl Similarity {
170 pub const EQUALS: &str = "equals";
171 pub const CONTAINS: &str = "contains";
172 pub const STARTS_WITH: &str = "starts-with";
173 pub const ENDS_WITH: &str = "ends-with";
174
175 pub const BETWEEN: &str = "between";
176 pub const LESSER: &str = "lesser";
177 pub const LESSER_OR_EQUAL: &str = "lesser-or-equal";
178 pub const GREATER: &str = "greater";
179 pub const GREATER_OR_EQUAL: &str = "greater-or-equal";
180}
181
182impl Default for Similarity {
183 fn default() -> Self {
184 Self::Equals
185 }
186}
187
188impl FromStr for Similarity {
189 type Err = Error;
190 fn from_str(s: &str) -> Result<Self> {
191 match s {
192 Similarity::EQUALS => Ok(Similarity::Equals),
193 Similarity::CONTAINS => Ok(Similarity::Contains),
194 Similarity::STARTS_WITH => Ok(Similarity::StartsWith),
195 Similarity::ENDS_WITH => Ok(Similarity::EndsWith),
196
197 Similarity::BETWEEN => Ok(Similarity::Between),
198 Similarity::LESSER => Ok(Similarity::Lesser),
199 Similarity::LESSER_OR_EQUAL => Ok(Similarity::LesserOrEqual),
200 Similarity::GREATER => Ok(Similarity::Greater),
201 Similarity::GREATER_OR_EQUAL => Ok(Similarity::GreaterOrEqual),
202
203 val => Err(Error::InvalidSimilarity(val.into())),
204 }
205 }
206}
207
208impl ToString for Similarity {
209 fn to_string(&self) -> String {
210 match self {
211 Self::Equals => Self::EQUALS.to_string(),
212 Self::Contains => Self::CONTAINS.to_string(),
213 Self::StartsWith => Self::STARTS_WITH.to_string(),
214 Self::EndsWith => Self::ENDS_WITH.to_string(),
215
216 Self::Between => Self::BETWEEN.to_string(),
217 Self::Lesser => Self::LESSER.to_string(),
218 Self::LesserOrEqual => Self::LESSER_OR_EQUAL.to_string(),
219 Self::Greater => Self::GREATER.to_string(),
220 Self::GreaterOrEqual => Self::GREATER_OR_EQUAL.to_string(),
221 }
222 }
223}
224
225#[derive(Clone, Debug, PartialEq)]
226pub struct Parameter {
227 pub similarity: Similarity,
228 pub values: Vec<String>,
229}
230
231impl Parameter {
232 pub fn new() -> Self {
233 Self {
234 similarity: Similarity::default(),
235 values: vec![],
236 }
237 }
238
239 pub fn init(similarity: Similarity, values: Vec<String>) -> Self {
240 Self { similarity, values }
241 }
242}
243
244impl FromStr for Parameter {
245 type Err = Error;
246
247 fn from_str(s: &str) -> Result<Self> {
258 let trimmed = s.trim();
259 if trimmed.is_empty() {
260 return Err(Error::InvalidParameter(s.into()));
261 }
262
263 let parts: Vec<&str> = trimmed.split(COLON).collect();
264 if parts.len() != 2 {
265 return Err(Error::InvalidParameter(s.into()));
266 }
267
268 let similarity_str = parts[0].trim();
269 let values_str = parts[1].trim();
270
271 if similarity_str.is_empty() {
272 return Err(Error::InvalidParameter(s.into()));
273 }
274
275 let values: Vec<String> = if values_str.is_empty() {
276 vec![]
277 } else {
278 values_str
279 .split(COMMA)
280 .map(|v| url_decode(v.trim()))
281 .filter(|v| !v.is_empty())
282 .collect()
283 };
284
285 Ok(Parameter::init(
286 Similarity::from_str(similarity_str)?,
287 values,
288 ))
289 }
290}
291
292#[derive(Clone, Debug, PartialEq)]
293pub struct Parameters(pub IndexMap<String, Parameter>);
294
295impl Parameters {
296 pub const ORDER: &str = "order";
297 pub const LIMIT: &str = "limit";
298 pub const OFFSET: &str = "offset";
299
300 pub const EXCLUDE: [&str; 3] = [Parameters::ORDER, Parameters::LIMIT, Parameters::OFFSET];
301
302 pub const DEFAULT_LIMIT: usize = 50;
303 pub const DEFAULT_OFFSET: usize = 0;
304
305 pub const MAX_LIMIT: usize = 100;
306
307 pub fn new() -> Self {
308 Self(IndexMap::new())
309 }
310}
311
312impl Default for Parameters {
313 fn default() -> Self {
314 Self::new()
315 }
316}
317
318impl FromStr for Parameters {
319 type Err = Error;
320
321 fn from_str(s: &str) -> Result<Self> {
324 let trimmed = s.trim();
325 if trimmed.is_empty() {
326 return Ok(Parameters::new());
327 }
328
329 let str_parameters: Vec<&str> = trimmed.split(AMPERSAND).collect();
330 let mut parameters: Self = Parameters(IndexMap::new());
331
332 for str_param in str_parameters {
333 let trimmed_param = str_param.trim();
334 if trimmed_param.is_empty() {
335 continue;
336 }
337
338 let mut parts = trimmed_param.splitn(2, EQUAL);
339 let (key, value) = match (parts.next(), parts.next()) {
340 (Some(k), Some(v)) => (k, v),
341 _ => return Err(Error::InvalidParameter(trimmed_param.into())),
342 };
343
344 let trimmed_key = key.trim();
345 if trimmed_key.is_empty() || Parameters::EXCLUDE.contains(&trimmed_key) {
346 continue;
347 }
348
349 let param = Parameter::from_str(value)?;
350 if param.values.is_empty() {
352 continue;
353 }
354
355 parameters.0.insert(trimmed_key.to_string(), param);
356 }
357
358 Ok(parameters)
359 }
360}
361
362#[derive(Clone, Debug, PartialEq)]
363pub struct Query {
364 pub parameters: Parameters,
365 pub sort_fields: SortFields,
366 pub limit: usize,
367 pub offset: usize,
368}
369
370impl Query {
371 pub fn new() -> Self {
372 Self {
373 parameters: Parameters::new(),
374 sort_fields: SortFields::new(),
375 limit: Parameters::DEFAULT_LIMIT,
376 offset: Parameters::DEFAULT_OFFSET,
377 }
378 }
379
380 pub fn init(
381 parameters: Parameters,
382 sort_fields: SortFields,
383 limit: usize,
384 offset: usize,
385 ) -> Self {
386 Self {
387 parameters,
388 sort_fields,
389 limit,
390 offset,
391 }
392 }
393
394 pub fn to_http(&self) -> String {
395 let mut params = self
396 .parameters
397 .0
398 .iter()
399 .filter(|(_, param)| param.values.len() > 0)
400 .map(|(key, param)| {
401 let similarity = param.similarity.to_string();
402 let values = param
403 .values
404 .iter()
405 .map(|v| url_encode(v))
406 .collect::<Vec<String>>()
407 .join(&format!("{COMMA}"));
408 format!("{key}{EQUAL}{similarity}{COLON}{values}",)
409 })
410 .collect::<Vec<String>>()
411 .join("&");
412
413 let order = self
414 .sort_fields
415 .0
416 .iter()
417 .filter(|field| field.name.len() > 0)
418 .map(|field| {
419 let name = field.name.clone();
420 let order = field.order.to_string();
421 format!("{name}{COLON}{order}")
422 })
423 .collect::<Vec<String>>()
424 .join(&format!("{COMMA}"));
425
426 if params.len() > 0 {
427 params.push_str(&format!("{AMPERSAND}"));
428 }
429
430 if order.len() > 0 {
431 params.push_str(&order);
432 params.push_str(&format!("{AMPERSAND}"));
433 }
434
435 format!(
436 "{params}{}{EQUAL}{}{AMPERSAND}{}{EQUAL}{}",
437 Parameters::LIMIT,
438 self.limit,
439 Parameters::OFFSET,
440 self.offset,
441 )
442 }
443
444 pub fn from_http(search: String) -> Result<Self> {
446 let mut query = Self::new();
447 let trimmed_search = search.trim_start_matches(QUESTION).trim();
448
449 if trimmed_search.is_empty() {
450 return Ok(query);
451 }
452
453 for k_v in trimmed_search.split(AMPERSAND) {
454 let trimmed_kv = k_v.trim();
455 if trimmed_kv.is_empty() {
456 continue;
457 }
458
459 let mut parts = trimmed_kv.splitn(2, EQUAL);
460 if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
461 let trimmed_key = key.trim();
462 let trimmed_value = value.trim();
463
464 if trimmed_key.is_empty() {
465 continue;
466 }
467
468 match trimmed_key {
469 Parameters::ORDER => {
470 if trimmed_value.is_empty() {
471 continue;
472 }
473
474 if !trimmed_value.contains(COLON) {
476 return Err(Error::InvalidSortField(trimmed_value.into()));
478 }
479
480 if let Ok(sort_fields) = SortFields::from_str(trimmed_value) {
481 query.sort_fields = sort_fields;
482 }
483 }
485 Parameters::LIMIT => {
486 if trimmed_value.is_empty() {
487 continue;
488 }
489
490 let limit: usize =
491 trimmed_value.parse().unwrap_or(Parameters::DEFAULT_LIMIT);
492 query.limit = limit.min(Parameters::MAX_LIMIT);
493 }
494 Parameters::OFFSET => {
495 if trimmed_value.is_empty() {
496 continue;
497 }
498
499 query.offset = trimmed_value.parse().unwrap_or(Parameters::DEFAULT_OFFSET);
500 }
501 _k => {
502 if trimmed_value.is_empty() {
503 continue;
504 }
505
506 if trimmed_value.contains(COLON) {
508 let param = Parameter::from_str(trimmed_value)?;
510 if param.values.is_empty() {
512 continue;
513 }
514 query.parameters.0.insert(trimmed_key.to_string(), param);
516 } else {
517 let decoded_value = url_decode(trimmed_value);
519
520 if let Some(existing_param) = query.parameters.0.get_mut(&trimmed_key.to_string()) {
522 if existing_param.similarity == Similarity::Equals {
524 existing_param.values.push(decoded_value);
525 }
526 } else {
528 let param = Parameter::init(Similarity::Equals, vec![decoded_value]);
530 query.parameters.0.insert(trimmed_key.to_string(), param);
531 }
532 }
533 }
534 }
535 } else {
536 return Err(Error::InvalidSearchParameters(search));
537 }
538 }
539
540 Ok(query)
541 }
542
543 pub fn keep(&self, keys: Vec<String>) -> Self {
544 let mut clone = self.clone();
545 let keys_to_remove: Vec<String> = self
546 .parameters
547 .0
548 .keys()
549 .filter(|k| !keys.contains(k))
550 .map(|k| k.clone())
551 .collect();
552
553 for k in keys_to_remove {
554 clone.parameters.0.shift_remove(&k);
555 }
556
557 clone
558 }
559
560 pub fn remove(&self, keys: Vec<String>) -> Self {
561 let mut clone = self.clone();
562 let keys_to_remove: Vec<String> = self
563 .parameters
564 .0
565 .keys()
566 .filter(|k| keys.contains(k))
567 .map(|k| k.clone())
568 .collect();
569
570 for k in keys_to_remove {
571 clone.parameters.0.shift_remove(&k);
572 }
573
574 clone
575 }
576
577 #[cfg(feature = "sql")]
578 pub fn to_sql(&self) -> String {
579 let mut sql_parts = Vec::new();
580
581 let where_clause = self.build_where_clause();
583 if !where_clause.is_empty() {
584 sql_parts.push(format!("WHERE {}", where_clause));
585 }
586
587 let order_clause = self.build_order_clause();
589 if !order_clause.is_empty() {
590 sql_parts.push(format!("ORDER BY {}", order_clause));
591 }
592
593 sql_parts.push(format!("LIMIT ? OFFSET ?"));
595
596 sql_parts.join(" ")
597 }
598
599 #[cfg(feature = "sql")]
600 fn build_where_clause(&self) -> String {
601 let mut conditions = Vec::new();
602
603 for (key, param) in &self.parameters.0 {
604 if param.values.is_empty() {
605 continue;
606 }
607
608 let condition = match param.similarity {
609 Similarity::Equals => {
610 if param.values.len() == 1 {
611 if param.values[0] == "null" {
612 format!("{} IS ?", key)
613 } else {
614 format!("{} = ?", key)
615 }
616 } else {
617 let placeholders = vec!["?"; param.values.len()].join(", ");
618 format!("{} IN ({})", key, placeholders)
619 }
620 }
621 Similarity::Contains => {
622 if param.values.len() == 1 {
623 format!("{} LIKE ?", key)
624 } else {
625 let like_conditions: Vec<String> = param.values.iter()
626 .map(|_| format!("{} LIKE ?", key))
627 .collect();
628 format!("({})", like_conditions.join(" OR "))
629 }
630 }
631 Similarity::StartsWith => {
632 if param.values.len() == 1 {
633 format!("{} LIKE ?", key)
634 } else {
635 let like_conditions: Vec<String> = param.values.iter()
636 .map(|_| format!("{} LIKE ?", key))
637 .collect();
638 format!("({})", like_conditions.join(" OR "))
639 }
640 }
641 Similarity::EndsWith => {
642 if param.values.len() == 1 {
643 format!("{} LIKE ?", key)
644 } else {
645 let like_conditions: Vec<String> = param.values.iter()
646 .map(|_| format!("{} LIKE ?", key))
647 .collect();
648 format!("({})", like_conditions.join(" OR "))
649 }
650 }
651 Similarity::Between => {
652 if param.values.len() >= 2 {
653 let pairs: Vec<&[String]> = param.values.chunks(2).collect();
655 let between_conditions: Vec<String> = pairs.iter()
656 .map(|pair| {
657 if pair.len() == 2 {
658 format!("{} BETWEEN ? AND ?", key)
659 } else {
660 String::new() }
662 })
663 .filter(|condition| !condition.is_empty())
664 .collect();
665
666 if between_conditions.is_empty() {
667 continue; } else if between_conditions.len() == 1 {
669 between_conditions[0].clone()
670 } else {
671 format!("({})", between_conditions.join(" OR "))
672 }
673 } else {
674 continue; }
676 }
677 Similarity::Lesser => {
678 if param.values.len() == 1 {
679 format!("{} < ?", key)
680 } else {
681 let conditions: Vec<String> = param.values.iter()
682 .map(|_| format!("{} < ?", key))
683 .collect();
684 format!("({})", conditions.join(" OR "))
685 }
686 }
687 Similarity::LesserOrEqual => {
688 if param.values.len() == 1 {
689 format!("{} <= ?", key)
690 } else {
691 let conditions: Vec<String> = param.values.iter()
692 .map(|_| format!("{} <= ?", key))
693 .collect();
694 format!("({})", conditions.join(" OR "))
695 }
696 }
697 Similarity::Greater => {
698 if param.values.len() == 1 {
699 format!("{} > ?", key)
700 } else {
701 let conditions: Vec<String> = param.values.iter()
702 .map(|_| format!("{} > ?", key))
703 .collect();
704 format!("({})", conditions.join(" OR "))
705 }
706 }
707 Similarity::GreaterOrEqual => {
708 if param.values.len() == 1 {
709 format!("{} >= ?", key)
710 } else {
711 let conditions: Vec<String> = param.values.iter()
712 .map(|_| format!("{} >= ?", key))
713 .collect();
714 format!("({})", conditions.join(" OR "))
715 }
716 }
717 };
718
719 conditions.push(condition);
720 }
721
722 conditions.join(" AND ")
723 }
724
725 #[cfg(feature = "sql")]
726 fn build_order_clause(&self) -> String {
727 let mut order_parts = Vec::new();
728
729 for field in &self.sort_fields.0 {
730 if !field.name.is_empty() {
731 let direction = match field.order {
732 SortOrder::Ascending => "ASC",
733 SortOrder::Descending => "DESC",
734 };
735 order_parts.push(format!("{} {}", field.name, direction));
736 }
737 }
738
739 order_parts.join(", ")
740 }
741}