rqb 0.1.7

sqlx-first typed Postgres query builder.
Documentation
use serde_json::Value as JsonValue;

use crate::{Error, Result};
use crate::{JsonKind, Param};

pub(super) fn json_array<'a>(
    field: &str,
    value: &'a JsonValue,
    expected: &'static str,
) -> Result<&'a Vec<JsonValue>> {
    value
        .as_array()
        .ok_or_else(|| Error::invalid_search_value(field, expected))
}

pub(super) fn json_param(field: &str, kind: JsonKind, value: &JsonValue) -> Result<Param> {
    match kind {
        JsonKind::Text => value
            .as_str()
            .map(|value| Param::typed(value.to_owned()))
            .ok_or_else(|| invalid_value(field, "string")),
        JsonKind::Bool => value
            .as_bool()
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "boolean")),
        JsonKind::Integer => value
            .as_i64()
            .and_then(|value| i32::try_from(value).ok())
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "32-bit integer")),
        JsonKind::BigInt => value
            .as_i64()
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "64-bit integer")),
        JsonKind::Float => value
            .as_f64()
            .filter(|value| value.is_finite())
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "finite number")),
        JsonKind::NumericString => value
            .as_str()
            .and_then(|value| value.parse::<sqlx::types::BigDecimal>().ok())
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "decimal string")),
        JsonKind::Uuid => value
            .as_str()
            .and_then(|value| value.parse::<uuid::Uuid>().ok())
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "UUID string")),
        JsonKind::Date => value
            .as_str()
            .and_then(|value| chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d").ok())
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "date string")),
        JsonKind::Time => value
            .as_str()
            .and_then(|value| chrono::NaiveTime::parse_from_str(value, "%H:%M:%S%.f").ok())
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "time string")),
        JsonKind::Timestamp => value
            .as_str()
            .and_then(parse_naive_datetime)
            .map(Param::typed)
            .ok_or_else(|| invalid_value(field, "timestamp string")),
        JsonKind::Timestamptz => value
            .as_str()
            .and_then(|value| chrono::DateTime::parse_from_rfc3339(value).ok())
            .map(|value| Param::typed(value.with_timezone(&chrono::Utc)))
            .ok_or_else(|| invalid_value(field, "RFC3339 timestamp string")),
        JsonKind::Jsonb => Ok(Param::typed(value.clone())),
    }
}

fn parse_naive_datetime(value: &str) -> Option<chrono::NaiveDateTime> {
    chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H:%M:%S%.f")
        .or_else(|_| chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S%.f"))
        .ok()
}

fn invalid_value(field: &str, expected: &'static str) -> Error {
    Error::invalid_search_value(field, expected)
}