use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Param {
Null,
Bool(bool),
Int(i128),
UInt(u128),
Float(f64),
Decimal(String),
Bytes(Vec<u8>),
Text(String),
}
impl Param {
pub fn decimal(text: impl Into<String>) -> Result<Param> {
let text = text.into();
validate_decimal(&text)?;
Ok(Param::Decimal(text))
}
pub fn bytes(bytes: impl Into<Vec<u8>>) -> Param {
Param::Bytes(bytes.into())
}
pub fn text(text: impl Into<String>) -> Param {
Param::Text(text.into())
}
pub(crate) fn validate(&self, index: usize) -> Result<()> {
match self {
Param::Float(f) if !f.is_finite() => Err(Error::invalid(format!(
"parameter {} is {f}, which has no SQL representation; only finite floats can be bound",
index + 1
))),
Param::Decimal(d) => validate_decimal(d),
_ => Ok(()),
}
}
}
fn validate_decimal(text: &str) -> Result<()> {
let body = text.strip_prefix(['-', '+']).unwrap_or(text);
let (int, frac) = match body.split_once('.') {
Some((i, f)) => (i, Some(f)),
None => (body, None),
};
let digits = |s: &str| s.bytes().all(|b| b.is_ascii_digit());
let ok = digits(int)
&& frac.is_none_or(digits)
&& (!int.is_empty() || frac.is_some_and(|f| !f.is_empty()))
&& !(int.is_empty() && frac.is_none());
if ok {
Ok(())
} else {
Err(Error::invalid(format!(
"`{text}` is not a plain decimal: use digits with an optional sign and fractional part, and no exponent"
)))
}
}
fn hex(bytes: &[u8]) -> String {
const DIGITS: &[u8; 16] = b"0123456789abcdef";
let mut s = String::with_capacity(2 + bytes.len() * 2);
s.push_str("0x");
for b in bytes {
s.push(DIGITS[(b >> 4) as usize] as char);
s.push(DIGITS[(b & 0x0f) as usize] as char);
}
s
}
impl Serialize for Param {
fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
match self {
Param::Null => s.serialize_none(),
Param::Bool(b) => s.serialize_bool(*b),
Param::Int(i) => match i64::try_from(*i) {
Ok(v) => s.serialize_i64(v),
Err(_) => s.serialize_i128(*i),
},
Param::UInt(u) => match u64::try_from(*u) {
Ok(v) => s.serialize_u64(v),
Err(_) => s.serialize_u128(*u),
},
Param::Float(f) => s.serialize_f64(*f),
Param::Decimal(d) => s.serialize_str(d),
Param::Bytes(b) => s.serialize_str(&hex(b)),
Param::Text(t) => s.serialize_str(t),
}
}
}
macro_rules! from_int {
($variant:ident, $wide:ty, $($t:ty),*) => {
$(impl From<$t> for Param {
fn from(v: $t) -> Self {
Param::$variant(<$wide>::from(v))
}
})*
};
}
from_int!(Int, i128, i8, i16, i32, i64, i128);
from_int!(UInt, u128, u8, u16, u32, u64, u128);
impl From<isize> for Param {
fn from(v: isize) -> Self {
Param::Int(v as i128)
}
}
impl From<usize> for Param {
fn from(v: usize) -> Self {
Param::UInt(v as u128)
}
}
impl From<bool> for Param {
fn from(v: bool) -> Self {
Param::Bool(v)
}
}
impl From<f64> for Param {
fn from(v: f64) -> Self {
Param::Float(v)
}
}
impl From<f32> for Param {
fn from(v: f32) -> Self {
Param::Float(f64::from(v))
}
}
impl From<&str> for Param {
fn from(v: &str) -> Self {
Param::Text(v.to_string())
}
}
impl From<String> for Param {
fn from(v: String) -> Self {
Param::Text(v)
}
}
impl From<&String> for Param {
fn from(v: &String) -> Self {
Param::Text(v.clone())
}
}
impl From<Vec<u8>> for Param {
fn from(v: Vec<u8>) -> Self {
Param::Bytes(v)
}
}
impl From<&[u8]> for Param {
fn from(v: &[u8]) -> Self {
Param::Bytes(v.to_vec())
}
}
impl<T: Into<Param>> From<Option<T>> for Param {
fn from(v: Option<T>) -> Self {
v.map_or(Param::Null, Into::into)
}
}
#[macro_export]
macro_rules! params {
() => { ::std::vec::Vec::<$crate::Param>::new() };
($($v:expr),+ $(,)?) => { ::std::vec![$($crate::Param::from($v)),+] };
}
#[cfg(test)]
mod tests {
use super::*;
fn wire(p: &[Param]) -> String {
serde_json::to_string(p).unwrap()
}
#[test]
fn scalars_encode_as_json_scalars() {
assert_eq!(
wire(¶ms![None::<i32>, true, 42, -7i64, 1.5, "O'Brien"]),
r#"[null,true,42,-7,1.5,"O'Brien"]"#
);
}
#[test]
fn wide_integers_are_exact_json_numbers() {
assert_eq!(wire(¶ms![i64::MAX]), "[9223372036854775807]");
assert_eq!(wire(¶ms![u64::MAX]), "[18446744073709551615]");
assert_eq!(
wire(¶ms![170141183460469231731687303715884105727i128]),
"[170141183460469231731687303715884105727]"
);
assert_eq!(
wire(¶ms![u128::MAX]),
"[340282366920938463463374607431768211455]"
);
assert_eq!(
wire(¶ms![-9223372036854775809i128]),
"[-9223372036854775809]"
);
}
#[test]
fn decimals_are_plain_text_without_an_exponent() {
let d = Param::decimal("0.123456789012345678").unwrap();
assert_eq!(wire(&[d]), r#"["0.123456789012345678"]"#);
assert!(Param::decimal("-1.50").is_ok());
assert!(Param::decimal("+10").is_ok());
assert!(Param::decimal(".5").is_ok());
assert!(Param::decimal("5.").is_ok());
for bad in ["1.5E+3", "1e10", "", "-", ".", "1.2.3", "12a", "NaN", " 1"] {
assert!(Param::decimal(bad).is_err(), "{bad} should be refused");
}
}
#[test]
fn bytes_are_lowercase_hex_text() {
assert_eq!(
wire(&[Param::bytes(vec![0x00, 0xab, 0xff, 0x10])]),
r#"["0x00abff10"]"#
);
assert_eq!(wire(¶ms![Vec::<u8>::new()]), r#"["0x"]"#);
let invalid_utf8: &[u8] = &[0xc3, 0x28];
assert_eq!(wire(¶ms![invalid_utf8]), r#"["0xc328"]"#);
}
#[test]
fn non_finite_floats_are_refused_by_name() {
for f in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let err = Param::Float(f).validate(2).unwrap_err();
assert_eq!(err.kind, crate::ErrorKind::InvalidArgument);
assert!(err.message.contains("parameter 3"), "{}", err.message);
}
assert!(Param::Float(1.0).validate(0).is_ok());
}
#[test]
fn f32_widens_without_changing_the_value() {
assert_eq!(wire(¶ms![0.5f32]), "[0.5]");
}
}