Skip to main content

systemprompt_identifiers/db_value/
from_value.rs

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