query_lite/
lib.rs

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