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) => Ok(serde_json::Value::from(arr.clone()).to_string()),
19            DbValue::NullString
20            | DbValue::NullInt
21            | DbValue::NullFloat
22            | DbValue::NullBool
23            | DbValue::NullBytes
24            | DbValue::NullTimestamp
25            | DbValue::NullStringArray => Err(DbValueError::null_for("String")),
26            DbValue::Bytes(_) => Err(DbValueError::incompatible("Bytes", "String")),
27        }
28    }
29}
30
31impl FromDbValue for i64 {
32    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
33        match value {
34            DbValue::Int(i) => Ok(*i),
35            DbValue::Float(f) => f64_to_i64_checked(*f),
36            DbValue::Bool(b) => Ok(Self::from(*b)),
37            DbValue::String(s) => s
38                .parse()
39                .map_err(|_e| DbValueError::parse(s.clone(), "i64")),
40            DbValue::StringArray(_) => Err(DbValueError::incompatible("StringArray", "i64")),
41            DbValue::Timestamp(_) => Err(DbValueError::incompatible("Timestamp", "i64")),
42            DbValue::NullString
43            | DbValue::NullInt
44            | DbValue::NullFloat
45            | DbValue::NullBool
46            | DbValue::NullBytes
47            | DbValue::NullTimestamp
48            | DbValue::NullStringArray => Err(DbValueError::null_for("i64")),
49            DbValue::Bytes(_) => Err(DbValueError::incompatible("Bytes", "i64")),
50        }
51    }
52}
53
54impl FromDbValue for i32 {
55    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
56        i64::from_db_value(value)
57            .and_then(|v| Self::try_from(v).map_err(|_e| DbValueError::out_of_range("i32")))
58    }
59}
60
61impl FromDbValue for u64 {
62    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
63        i64::from_db_value(value)
64            .and_then(|v| Self::try_from(v).map_err(|_e| DbValueError::out_of_range("u64")))
65    }
66}
67
68impl FromDbValue for u32 {
69    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
70        i64::from_db_value(value)
71            .and_then(|v| Self::try_from(v).map_err(|_e| DbValueError::out_of_range("u32")))
72    }
73}
74
75const I64_MAX_SAFE_F64: i64 = 1 << 53;
76
77const fn i64_to_f64_checked(value: i64) -> Result<f64, DbValueError> {
78    if value.abs() > I64_MAX_SAFE_F64 {
79        return Err(DbValueError::out_of_range("f64"));
80    }
81    Ok(value as f64)
82}
83
84fn f64_to_i64_checked(value: f64) -> Result<i64, DbValueError> {
85    if value.is_nan() || value.is_infinite() {
86        return Err(DbValueError::incompatible("NaN/Infinite", "i64"));
87    }
88    if value < (i64::MIN as f64) || value > (i64::MAX as f64) {
89        return Err(DbValueError::out_of_range("i64"));
90    }
91    Ok(value as i64)
92}
93
94impl FromDbValue for f64 {
95    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
96        match value {
97            DbValue::Float(f) => Ok(*f),
98            DbValue::Int(i) => i64_to_f64_checked(*i),
99            DbValue::String(s) => s
100                .parse()
101                .map_err(|_e| DbValueError::parse(s.clone(), "f64")),
102            DbValue::StringArray(_) => Err(DbValueError::incompatible("StringArray", "f64")),
103            DbValue::Timestamp(_) => Err(DbValueError::incompatible("Timestamp", "f64")),
104            DbValue::NullString
105            | DbValue::NullInt
106            | DbValue::NullFloat
107            | DbValue::NullBool
108            | DbValue::NullBytes
109            | DbValue::NullTimestamp
110            | DbValue::NullStringArray => Err(DbValueError::null_for("f64")),
111            DbValue::Bool(_) => Err(DbValueError::incompatible("Bool", "f64")),
112            DbValue::Bytes(_) => Err(DbValueError::incompatible("Bytes", "f64")),
113        }
114    }
115}
116
117impl FromDbValue for bool {
118    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
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(DbValueError::parse(s.clone(), "bool")),
126            },
127            DbValue::StringArray(_) => Err(DbValueError::incompatible("StringArray", "bool")),
128            DbValue::Timestamp(_) => Err(DbValueError::incompatible("Timestamp", "bool")),
129            DbValue::NullString
130            | DbValue::NullInt
131            | DbValue::NullFloat
132            | DbValue::NullBool
133            | DbValue::NullBytes
134            | DbValue::NullTimestamp
135            | DbValue::NullStringArray => Err(DbValueError::null_for("bool")),
136            DbValue::Float(_) => Err(DbValueError::incompatible("Float", "bool")),
137            DbValue::Bytes(_) => Err(DbValueError::incompatible("Bytes", "bool")),
138        }
139    }
140}
141
142impl FromDbValue for Vec<u8> {
143    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
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(DbValueError::null_for("Vec<u8>")),
154            DbValue::Int(_)
155            | DbValue::Float(_)
156            | DbValue::Bool(_)
157            | DbValue::Timestamp(_)
158            | DbValue::StringArray(_) => Err(DbValueError::incompatible("non-bytes", "Vec<u8>")),
159        }
160    }
161}
162
163impl<T: FromDbValue> FromDbValue for Option<T> {
164    fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
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, DbValueError> {
186        match value {
187            DbValue::String(s) => parse_database_datetime(&serde_json::Value::String(s.clone()))
188                .ok_or_else(|| DbValueError::parse(s.clone(), "DateTime<Utc>")),
189            DbValue::Timestamp(dt) => Ok(*dt),
190            DbValue::Int(ts) => Self::from_timestamp(*ts, 0)
191                .ok_or_else(|| DbValueError::parse(ts.to_string(), "DateTime<Utc>")),
192            DbValue::NullString
193            | DbValue::NullInt
194            | DbValue::NullFloat
195            | DbValue::NullBool
196            | DbValue::NullBytes
197            | DbValue::NullTimestamp
198            | DbValue::NullStringArray => Err(DbValueError::null_for("DateTime<Utc>")),
199            DbValue::Float(_) | DbValue::Bool(_) | DbValue::Bytes(_) | DbValue::StringArray(_) => {
200                Err(DbValueError::incompatible("non-datetime", "DateTime<Utc>"))
201            },
202        }
203    }
204}