Skip to main content

trailbase_qs/
column_rel_value.rs

1use base64::prelude::*;
2use serde::de::{Deserializer, Error};
3
4use crate::value::Value;
5
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub enum CompareOp {
8  Equal,
9  NotEqual,
10  GreaterThanEqual,
11  GreaterThan,
12  LessThanEqual,
13  LessThan,
14  Is,
15  Like,
16  Regexp,
17}
18
19impl CompareOp {
20  pub fn from(qualifier: &str) -> Option<Self> {
21    return match qualifier {
22      "$eq" => Some(Self::Equal),
23      "$ne" => Some(Self::NotEqual),
24      "$gte" => Some(Self::GreaterThanEqual),
25      "$gt" => Some(Self::GreaterThan),
26      "$lte" => Some(Self::LessThanEqual),
27      "$lt" => Some(Self::LessThan),
28      "$is" => Some(Self::Is),
29      "$like" => Some(Self::Like),
30      "$re" => Some(Self::Regexp),
31      _ => None,
32    };
33  }
34
35  #[inline]
36  pub fn as_sql(&self) -> &'static str {
37    return match self {
38      Self::GreaterThanEqual => ">=",
39      Self::GreaterThan => ">",
40      Self::LessThanEqual => "<=",
41      Self::LessThan => "<",
42      Self::NotEqual => "<>",
43      Self::Is => "IS",
44      Self::Like => "LIKE",
45      Self::Regexp => "REGEXP",
46      Self::Equal => "=",
47    };
48  }
49
50  #[inline]
51  pub fn as_query(&self) -> &'static str {
52    return match self {
53      Self::Equal => "$eq",
54      Self::NotEqual => "$ne",
55      Self::GreaterThanEqual => "$gte",
56      Self::GreaterThan => "$gt",
57      Self::LessThanEqual => "$lte",
58      Self::LessThan => "$lt",
59      Self::Is => "$is",
60      Self::Like => "$like",
61      Self::Regexp => "$re",
62    };
63  }
64}
65
66/// Type to support query of shape: `[column][op]=value`.
67#[derive(Clone, Debug, PartialEq)]
68pub struct ColumnOpValue {
69  pub column: String,
70  pub op: CompareOp,
71  pub value: Value,
72}
73
74fn parse_value<'de, D>(op: CompareOp, value: serde_value::Value) -> Result<Value, D::Error>
75where
76  D: Deserializer<'de>,
77{
78  use crate::util::unexpected;
79
80  return match op {
81    CompareOp::Is => match value {
82      serde_value::Value::String(value) if value == "NULL" => Ok(Value::String("NULL".to_string())),
83      serde_value::Value::String(value) if value == "!NULL" => {
84        Ok(Value::String("NOT NULL".to_string()))
85      }
86      _ => Err(Error::invalid_type(unexpected(&value), &"NULL or !NULL")),
87    },
88    _ => match value {
89      serde_value::Value::String(value) => Ok(Value::unparse(value)),
90      serde_value::Value::Bytes(bytes) => Ok(Value::String(BASE64_URL_SAFE.encode(bytes))),
91      serde_value::Value::I64(i) => Ok(Value::Integer(i)),
92      serde_value::Value::I32(i) => Ok(Value::Integer(i as i64)),
93      serde_value::Value::I16(i) => Ok(Value::Integer(i as i64)),
94      serde_value::Value::I8(i) => Ok(Value::Integer(i as i64)),
95      serde_value::Value::U64(i) => Ok(Value::Integer(i as i64)),
96      serde_value::Value::U32(i) => Ok(Value::Integer(i as i64)),
97      serde_value::Value::U16(i) => Ok(Value::Integer(i as i64)),
98      serde_value::Value::U8(i) => Ok(Value::Integer(i as i64)),
99      serde_value::Value::Bool(b) => Ok(Value::Integer(if b { 1 } else { 0 })),
100      _ => Err(Error::invalid_type(
101        unexpected(&value),
102        &"trailbase_qs::Value, i.e. string, integer, double or bool",
103      )),
104    },
105  };
106}
107
108pub fn serde_value_to_single_column_rel_value<'de, D>(
109  key: String,
110  value: serde_value::Value,
111) -> Result<ColumnOpValue, D::Error>
112where
113  D: Deserializer<'de>,
114{
115  use crate::util::unexpected;
116  use serde_value::Value;
117
118  if !crate::util::sanitize_column_name(&key) {
119    // NOTE: This may trigger if serde_qs parse depth is not enough. In this case, square brackets
120    // will end up in the column name.
121    return Err(Error::custom(format!(
122      "invalid column name for filter: {key}. Nesting too deep?"
123    )));
124  }
125
126  return match value {
127    // The simple ?filter[col]=val case.
128    Value::String(_) => Ok(ColumnOpValue {
129      column: key,
130      op: CompareOp::Equal,
131      value: parse_value::<D>(CompareOp::Equal, value)?,
132    }),
133    // The operator case ?filter[col][$ne]=val case.
134    Value::Map(mut m) if m.len() == 1 => {
135      let (k, v) = m.pop_first().expect("len() == 1");
136
137      let op = if let Value::String(ref op_str) = k {
138        CompareOp::from(op_str).ok_or_else(|| Error::invalid_type(unexpected(&k), &OP_ERR))?
139      } else {
140        return Err(Error::invalid_type(unexpected(&k), &OP_ERR));
141      };
142
143      Ok(ColumnOpValue {
144        column: key,
145        value: parse_value::<D>(op, v)?,
146        op,
147      })
148    }
149    v => Err(Error::invalid_type(
150      unexpected(&v),
151      &"[column_name]=value or [column_name][$op]=value",
152    )),
153  };
154}
155
156const OP_ERR: &str = "one of [$eq, $ne, $lt, ...]";