use either::Either;
use crate::{Arguments, Query, Value, error::EncodeError};
#[derive(Debug, Clone)]
pub struct Expr {
pub(crate) sql: String,
pub(crate) arguments: Arguments,
pub(crate) tainted: bool,
}
impl Expr {
pub fn sql(&self) -> &str {
&self.sql
}
pub fn tainted(&self) -> bool {
self.tainted
}
}
impl From<Query> for Expr {
fn from(query: Query) -> Self {
let sql = match query.statement {
Either::Left(sql) => sql,
Either::Right(statement) => statement.sql,
};
let arguments = query.arguments.unwrap_or_default();
Self {
sql,
arguments,
tainted: query.tainted,
}
}
}
pub fn now_rfc3339_utc() -> Expr {
Expr {
sql: "STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now')".to_string(),
arguments: Arguments::default(),
tainted: false,
}
}
pub fn jsonb(json: &str) -> Expr {
let mut arguments = Arguments::default();
arguments.values.push(Value::Text {
value: json.to_string().into(),
type_info: None,
});
Expr {
sql: "jsonb(?)".to_string(),
arguments,
tainted: false,
}
}
pub fn jsonb_text(json: &str) -> Expr {
jsonb(json)
}
pub fn jsonb_serde<T>(value: &T) -> crate::Result<Expr>
where
T: serde::Serialize + ?Sized,
{
let json = serde_json::to_string(value).map_err(|e| {
crate::Error::Encode(EncodeError::Conversion(format!(
"failed to encode value as JSON: {e}"
)))
})?;
let mut arguments = Arguments::default();
arguments.values.push(Value::Text {
value: json.into(),
type_info: None,
});
Ok(Expr {
sql: "jsonb(?)".to_string(),
arguments,
tainted: false,
})
}
pub fn raw(sql: &str) -> Expr {
Expr {
sql: sql.to_string(),
arguments: Arguments::default(),
tainted: true,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_expr_is_tainted() {
assert!(raw("1").tainted());
}
#[test]
fn now_expr_is_not_tainted() {
assert!(!now_rfc3339_utc().tainted());
}
#[test]
fn jsonb_serde_roundtrips() {
#[derive(serde::Serialize)]
struct Payload {
a: i32,
}
let expr = jsonb_serde(&Payload { a: 1 }).unwrap();
assert_eq!(expr.sql(), "jsonb(?)");
assert!(!expr.tainted());
}
}