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