datasources-postgres 0.1.51

Postgres datasource provider
Documentation
use bytes::{BufMut, BytesMut};
use chrono::{DateTime, Utc};
use datasources::Value;
use rust_decimal::Decimal;
use std::error::Error;
use std::str::FromStr;
use tokio_postgres::types::{IsNull, ToSql, Type};
use uuid::Uuid;

#[derive(Debug)]
pub struct PgValue(pub Value);

impl ToSql for PgValue {
    fn to_sql(
        &self,
        ty: &Type,
        out: &mut BytesMut,
    ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
        encode(&self.0, ty, out)
    }

    fn accepts(_: &Type) -> bool {
        true
    }

    fn to_sql_checked(
        &self,
        ty: &Type,
        out: &mut BytesMut,
    ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
        self.to_sql(ty, out)
    }
}

fn encode(
    value: &Value,
    ty: &Type,
    out: &mut BytesMut,
) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
    if matches!(value, Value::Null) {
        return Ok(IsNull::Yes);
    }
    match ty {
        ty if ty == &Type::INT2 => i16::try_from(int_of(value)?)?.to_sql(ty, out),
        ty if ty == &Type::INT4 => i32::try_from(int_of(value)?)?.to_sql(ty, out),
        ty if ty == &Type::INT8 => int_of(value)?.to_sql(ty, out),
        ty if ty == &Type::BOOL => bool_of(value)?.to_sql(ty, out),
        ty if ty == &Type::FLOAT4 => (float_of(value)? as f32).to_sql(ty, out),
        ty if ty == &Type::FLOAT8 => float_of(value)?.to_sql(ty, out),
        ty if ty == &Type::NUMERIC => decimal_of(value)?.to_sql(ty, out),
        ty if ty == &Type::TIMESTAMPTZ => datetime_of(value)?.to_sql(ty, out),
        ty if ty == &Type::TIMESTAMP => datetime_of(value)?.naive_utc().to_sql(ty, out),
        ty if ty == &Type::UNKNOWN => value.wire_text().to_sql(ty, out),
        ty if ty == &Type::UUID => uuid_of(value)?.to_sql(ty, out),
        ty if ty == &Type::BYTEA => binary_of(value)?.to_sql(ty, out),
        ty if ty == &Type::JSONB => {
            out.put_u8(1);
            out.extend_from_slice(value.wire_text().as_bytes());
            Ok(IsNull::No)
        }
        ty if ty == &Type::JSON => {
            out.extend_from_slice(value.wire_text().as_bytes());
            Ok(IsNull::No)
        }
        _ => value.wire_text().to_sql(ty, out),
    }
}

fn int_of(value: &Value) -> Result<i64, Box<dyn Error + Sync + Send>> {
    match value {
        Value::Int(number) => Ok(*number),
        Value::Text(text) => Ok(text.parse()?),
        Value::Decimal(number) => Ok(i64::try_from(*number)?),
        _ => Err(format!("not an int: {}", value.wire_text()).into()),
    }
}

fn bool_of(value: &Value) -> Result<bool, Box<dyn Error + Sync + Send>> {
    match value {
        Value::Bool(flag) => Ok(*flag),
        Value::Int(number) => Ok(*number != 0),
        Value::Text(text) => Ok(text.parse()?),
        _ => Err(format!("not a bool: {}", value.wire_text()).into()),
    }
}

fn float_of(value: &Value) -> Result<f64, Box<dyn Error + Sync + Send>> {
    match value {
        Value::Float(number) => Ok(*number),
        Value::Int(number) => Ok(*number as f64),
        Value::Text(text) => Ok(text.parse()?),
        Value::Decimal(number) => Ok(f64::try_from(*number)?),
        _ => Err(format!("not a float: {}", value.wire_text()).into()),
    }
}

fn decimal_of(value: &Value) -> Result<Decimal, Box<dyn Error + Sync + Send>> {
    match value {
        Value::Decimal(number) => Ok(*number),
        Value::Int(number) => Ok(Decimal::from(*number)),
        Value::Float(number) => Decimal::from_str(&number.to_string())
            .map_err(|err| Box::new(err) as Box<dyn Error + Sync + Send>),
        Value::Text(text) => Ok(text.parse()?),
        _ => Err(format!("not a decimal: {}", value.wire_text()).into()),
    }
}

fn datetime_of(value: &Value) -> Result<DateTime<Utc>, Box<dyn Error + Sync + Send>> {
    match value {
        Value::DateTime(stamp) => Ok(*stamp),
        Value::Text(text) => Ok(DateTime::parse_from_rfc3339(text)?.with_timezone(&Utc)),
        _ => Err(format!("not a datetime: {}", value.wire_text()).into()),
    }
}

fn uuid_of(value: &Value) -> Result<Uuid, Box<dyn Error + Sync + Send>> {
    match value {
        Value::Uuid(id) => Ok(*id),
        Value::Text(text) => Ok(text.parse()?),
        _ => Err(format!("not a uuid: {}", value.wire_text()).into()),
    }
}

fn binary_of(value: &Value) -> Result<Vec<u8>, Box<dyn Error + Sync + Send>> {
    match value {
        Value::Binary(bytes) => Ok(bytes.clone()),
        Value::Text(text) => Ok(base64::Engine::decode(
            &base64::engine::general_purpose::STANDARD,
            text,
        )?),
        _ => Err(format!("not binary: {}", value.wire_text()).into()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use datasources::Value;

    #[test]
    fn unknown_text_uuid_encodes_as_unknown() {
        let id = "550e8400-e29b-41d4-a716-446655440000";
        let mut encoded = BytesMut::new();
        let mut unknown = BytesMut::new();
        PgValue(Value::from(id))
            .to_sql(&Type::UNKNOWN, &mut encoded)
            .unwrap();
        id.to_string()
            .to_sql(&Type::UNKNOWN, &mut unknown)
            .unwrap();
        assert_eq!(encoded, unknown);
    }
}