Skip to main content

systemprompt_identifiers/db_value/
from_value.rs

1use anyhow::{anyhow, Result};
2use chrono::{DateTime, Utc};
3
4use super::{parse_database_datetime, DbValue};
5
6pub trait FromDbValue: Sized {
7    fn from_db_value(value: &DbValue) -> Result<Self>;
8}
9
10impl FromDbValue for String {
11    fn from_db_value(value: &DbValue) -> Result<Self> {
12        match value {
13            DbValue::String(s) => Ok(s.clone()),
14            DbValue::Int(i) => Ok(i.to_string()),
15            DbValue::Float(f) => Ok(f.to_string()),
16            DbValue::Bool(b) => Ok(b.to_string()),
17            DbValue::Timestamp(dt) => Ok(dt.to_rfc3339()),
18            DbValue::StringArray(arr) => {
19                Ok(serde_json::to_string(arr).unwrap_or_else(|_| "[]".to_string()))
20            },
21            DbValue::NullString
22            | DbValue::NullInt
23            | DbValue::NullFloat
24            | DbValue::NullBool
25            | DbValue::NullBytes
26            | DbValue::NullTimestamp
27            | DbValue::NullStringArray => Err(anyhow!("Cannot convert NULL to String")),
28            DbValue::Bytes(_) => Err(anyhow!("Cannot convert Bytes to String")),
29        }
30    }
31}
32
33impl FromDbValue for i64 {
34    fn from_db_value(value: &DbValue) -> Result<Self> {
35        match value {
36            DbValue::Int(i) => Ok(*i),
37            DbValue::Float(f) => f64_to_i64_checked(*f),
38            DbValue::Bool(b) => Ok(Self::from(*b)),
39            DbValue::String(s) => s.parse().map_err(|_| anyhow!("Cannot parse {s} as i64")),
40            DbValue::StringArray(_) => Err(anyhow!("Cannot convert StringArray to i64")),
41            DbValue::Timestamp(_) => Err(anyhow!("Cannot convert Timestamp to i64")),
42            DbValue::NullString
43            | DbValue::NullInt
44            | DbValue::NullFloat
45            | DbValue::NullBool
46            | DbValue::NullBytes
47            | DbValue::NullTimestamp
48            | DbValue::NullStringArray => Err(anyhow!("Cannot convert NULL to i64")),
49            DbValue::Bytes(_) => Err(anyhow!("Cannot convert Bytes to i64")),
50        }
51    }
52}
53
54impl FromDbValue for i32 {
55    fn from_db_value(value: &DbValue) -> Result<Self> {
56        i64::from_db_value(value).and_then(|v| {
57            Self::try_from(v).map_err(|_| anyhow!("Integer overflow converting to i32"))
58        })
59    }
60}
61
62impl FromDbValue for u64 {
63    fn from_db_value(value: &DbValue) -> Result<Self> {
64        i64::from_db_value(value).and_then(|v| {
65            Self::try_from(v).map_err(|_| anyhow!("Negative value cannot convert to u64"))
66        })
67    }
68}
69
70impl FromDbValue for u32 {
71    fn from_db_value(value: &DbValue) -> Result<Self> {
72        i64::from_db_value(value)
73            .and_then(|v| Self::try_from(v).map_err(|_| anyhow!("Value out of range for u32")))
74    }
75}
76
77const I64_MAX_SAFE_F64: i64 = 1 << 53;
78
79fn i64_to_f64_checked(value: i64) -> Result<f64> {
80    if value.abs() > I64_MAX_SAFE_F64 {
81        return Err(anyhow!("Integer {value} exceeds f64 precision range"));
82    }
83    Ok(value as f64)
84}
85
86fn f64_to_i64_checked(value: f64) -> Result<i64> {
87    if value.is_nan() || value.is_infinite() {
88        return Err(anyhow!("Cannot convert NaN/Infinite to i64"));
89    }
90    if value < (i64::MIN as f64) || value > (i64::MAX as f64) {
91        return Err(anyhow!("Float {value} out of range for i64"));
92    }
93    Ok(value as i64)
94}
95
96impl FromDbValue for f64 {
97    fn from_db_value(value: &DbValue) -> Result<Self> {
98        match value {
99            DbValue::Float(f) => Ok(*f),
100            DbValue::Int(i) => i64_to_f64_checked(*i),
101            DbValue::String(s) => s.parse().map_err(|_| anyhow!("Cannot parse {s} as f64")),
102            DbValue::StringArray(_) => Err(anyhow!("Cannot convert StringArray to f64")),
103            DbValue::Timestamp(_) => Err(anyhow!("Cannot convert Timestamp to f64")),
104            DbValue::NullString
105            | DbValue::NullInt
106            | DbValue::NullFloat
107            | DbValue::NullBool
108            | DbValue::NullBytes
109            | DbValue::NullTimestamp
110            | DbValue::NullStringArray => Err(anyhow!("Cannot convert NULL to f64")),
111            DbValue::Bool(_) => Err(anyhow!("Cannot convert Bool to f64")),
112            DbValue::Bytes(_) => Err(anyhow!("Cannot convert Bytes to f64")),
113        }
114    }
115}
116
117impl FromDbValue for bool {
118    fn from_db_value(value: &DbValue) -> Result<Self> {
119        match value {
120            DbValue::Bool(b) => Ok(*b),
121            DbValue::Int(i) => Ok(*i != 0),
122            DbValue::String(s) => match s.to_lowercase().as_str() {
123                "true" | "1" | "yes" => Ok(true),
124                "false" | "0" | "no" => Ok(false),
125                _ => Err(anyhow!("Cannot parse {s} as bool")),
126            },
127            DbValue::StringArray(_) => Err(anyhow!("Cannot convert StringArray to bool")),
128            DbValue::Timestamp(_) => Err(anyhow!("Cannot convert Timestamp to bool")),
129            DbValue::NullString
130            | DbValue::NullInt
131            | DbValue::NullFloat
132            | DbValue::NullBool
133            | DbValue::NullBytes
134            | DbValue::NullTimestamp
135            | DbValue::NullStringArray => Err(anyhow!("Cannot convert NULL to bool")),
136            DbValue::Float(_) => Err(anyhow!("Cannot convert Float to bool")),
137            DbValue::Bytes(_) => Err(anyhow!("Cannot convert Bytes to bool")),
138        }
139    }
140}
141
142impl FromDbValue for Vec<u8> {
143    fn from_db_value(value: &DbValue) -> Result<Self> {
144        match value {
145            DbValue::Bytes(b) => Ok(b.clone()),
146            DbValue::String(s) => Ok(s.as_bytes().to_vec()),
147            DbValue::NullString
148            | DbValue::NullInt
149            | DbValue::NullFloat
150            | DbValue::NullBool
151            | DbValue::NullBytes
152            | DbValue::NullTimestamp
153            | DbValue::NullStringArray => Err(anyhow!("Cannot convert NULL to Vec<u8>")),
154            DbValue::Int(_)
155            | DbValue::Float(_)
156            | DbValue::Bool(_)
157            | DbValue::Timestamp(_)
158            | DbValue::StringArray(_) => Err(anyhow!("Cannot convert {value:?} to Vec<u8>")),
159        }
160    }
161}
162
163impl<T: FromDbValue> FromDbValue for Option<T> {
164    fn from_db_value(value: &DbValue) -> Result<Self> {
165        match value {
166            DbValue::NullString
167            | DbValue::NullInt
168            | DbValue::NullFloat
169            | DbValue::NullBool
170            | DbValue::NullBytes
171            | DbValue::NullTimestamp
172            | DbValue::NullStringArray => Ok(None),
173            DbValue::String(_)
174            | DbValue::Int(_)
175            | DbValue::Float(_)
176            | DbValue::Bool(_)
177            | DbValue::Bytes(_)
178            | DbValue::Timestamp(_)
179            | DbValue::StringArray(_) => T::from_db_value(value).map(Some),
180        }
181    }
182}
183
184impl FromDbValue for DateTime<Utc> {
185    fn from_db_value(value: &DbValue) -> Result<Self> {
186        match value {
187            DbValue::String(s) => parse_database_datetime(&serde_json::Value::String(s.clone()))
188                .ok_or_else(|| anyhow!("Cannot parse {s} as DateTime<Utc>")),
189            DbValue::Timestamp(dt) => Ok(*dt),
190            DbValue::Int(ts) => {
191                Self::from_timestamp(*ts, 0).ok_or_else(|| anyhow!("Invalid Unix timestamp: {ts}"))
192            },
193            DbValue::NullString
194            | DbValue::NullInt
195            | DbValue::NullFloat
196            | DbValue::NullBool
197            | DbValue::NullBytes
198            | DbValue::NullTimestamp
199            | DbValue::NullStringArray => Err(anyhow!("Cannot convert NULL to DateTime<Utc>")),
200            DbValue::Float(_) | DbValue::Bool(_) | DbValue::Bytes(_) | DbValue::StringArray(_) => {
201                Err(anyhow!("Cannot convert {value:?} to DateTime<Utc>"))
202            },
203        }
204    }
205}