systemprompt_identifiers/db_value/
from_value.rs1use 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}