Skip to main content

apiplant_db/
value.rs

1//! Conversions between JSON (the wire format) and typed SQL values.
2
3use apiplant_core::FieldType;
4use sea_orm::sea_query::Value as SqlValue;
5
6/// Convert a JSON value into a typed SQL value for the given column type.
7/// Returns a human-readable error on a type mismatch (surfaced as a 400).
8pub fn json_to_sql(ty: FieldType, v: &serde_json::Value) -> Result<SqlValue, String> {
9    if v.is_null() {
10        return Ok(null_for(ty));
11    }
12    Ok(match ty {
13        FieldType::String | FieldType::Text => {
14            SqlValue::from(v.as_str().ok_or("expected a string")?.to_string())
15        }
16        FieldType::Integer => SqlValue::from(
17            i32::try_from(v.as_i64().ok_or("expected an integer")?)
18                .map_err(|_| "integer out of range")?,
19        ),
20        FieldType::BigInt => SqlValue::from(v.as_i64().ok_or("expected an integer")?),
21        FieldType::Float => SqlValue::from(v.as_f64().ok_or("expected a number")?),
22        FieldType::Boolean => SqlValue::from(v.as_bool().ok_or("expected a boolean")?),
23        FieldType::Uuid | FieldType::Reference => {
24            let s = v.as_str().ok_or("expected a UUID string")?;
25            SqlValue::from(uuid::Uuid::parse_str(s).map_err(|e| e.to_string())?)
26        }
27        FieldType::Timestamp => {
28            let s = v.as_str().ok_or("expected an RFC3339 timestamp")?;
29            SqlValue::from(chrono::DateTime::parse_from_rfc3339(s).map_err(|e| e.to_string())?)
30        }
31        FieldType::Json => SqlValue::from(v.clone()),
32    })
33}
34
35/// The correctly-typed SQL `NULL` for a column type (Postgres cares about the
36/// type of a bound null).
37pub fn null_for(ty: FieldType) -> SqlValue {
38    match ty {
39        FieldType::String | FieldType::Text => SqlValue::String(None),
40        FieldType::Integer => SqlValue::Int(None),
41        FieldType::BigInt => SqlValue::BigInt(None),
42        FieldType::Float => SqlValue::Double(None),
43        FieldType::Boolean => SqlValue::Bool(None),
44        FieldType::Uuid | FieldType::Reference => SqlValue::Uuid(None),
45        FieldType::Timestamp => SqlValue::ChronoDateTimeWithTimeZone(None),
46        FieldType::Json => SqlValue::Json(None),
47    }
48}
49
50/// Convert a raw query-string value (always a string) into a typed SQL value
51/// for a column, used for `?field=value` filtering.
52pub fn string_to_sql(ty: FieldType, s: &str) -> Result<SqlValue, String> {
53    Ok(match ty {
54        FieldType::String | FieldType::Text => SqlValue::from(s.to_string()),
55        FieldType::Integer => SqlValue::from(s.parse::<i32>().map_err(|_| "expected an integer")?),
56        FieldType::BigInt => SqlValue::from(s.parse::<i64>().map_err(|_| "expected an integer")?),
57        FieldType::Float => SqlValue::from(s.parse::<f64>().map_err(|_| "expected a number")?),
58        FieldType::Boolean => SqlValue::from(s.parse::<bool>().map_err(|_| "expected a boolean")?),
59        FieldType::Uuid | FieldType::Reference => {
60            SqlValue::from(uuid::Uuid::parse_str(s).map_err(|e| e.to_string())?)
61        }
62        FieldType::Timestamp => {
63            SqlValue::from(chrono::DateTime::parse_from_rfc3339(s).map_err(|e| e.to_string())?)
64        }
65        FieldType::Json => SqlValue::from(serde_json::Value::String(s.to_string())),
66    })
67}
68
69/// Best-effort conversion for *untyped* params coming from function `.so`s via
70/// the raw-query host callback (we don't know the target column type there).
71pub fn json_param(v: &serde_json::Value) -> SqlValue {
72    use serde_json::Value as J;
73    match v {
74        J::Null => SqlValue::String(None),
75        J::Bool(b) => SqlValue::from(*b),
76        J::Number(n) if n.is_i64() => SqlValue::from(n.as_i64().unwrap()),
77        J::Number(n) if n.is_u64() => SqlValue::from(n.as_u64().unwrap() as i64),
78        J::Number(n) => SqlValue::from(n.as_f64().unwrap()),
79        J::String(s) => SqlValue::from(s.clone()),
80        other => SqlValue::from(other.clone()),
81    }
82}