systemprompt_identifiers/db_value/
from_value.rs1use 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(|_| "[]".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(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.parse().map_err(|_| 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(|_| 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(|_| 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(|_| 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.parse().map_err(|_| DbValueError::parse(s.clone(), "f64")),
100 DbValue::StringArray(_) => Err(DbValueError::incompatible("StringArray", "f64")),
101 DbValue::Timestamp(_) => Err(DbValueError::incompatible("Timestamp", "f64")),
102 DbValue::NullString
103 | DbValue::NullInt
104 | DbValue::NullFloat
105 | DbValue::NullBool
106 | DbValue::NullBytes
107 | DbValue::NullTimestamp
108 | DbValue::NullStringArray => Err(DbValueError::null_for("f64")),
109 DbValue::Bool(_) => Err(DbValueError::incompatible("Bool", "f64")),
110 DbValue::Bytes(_) => Err(DbValueError::incompatible("Bytes", "f64")),
111 }
112 }
113}
114
115impl FromDbValue for bool {
116 fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
117 match value {
118 DbValue::Bool(b) => Ok(*b),
119 DbValue::Int(i) => Ok(*i != 0),
120 DbValue::String(s) => match s.to_lowercase().as_str() {
121 "true" | "1" | "yes" => Ok(true),
122 "false" | "0" | "no" => Ok(false),
123 _ => Err(DbValueError::parse(s.clone(), "bool")),
124 },
125 DbValue::StringArray(_) => Err(DbValueError::incompatible("StringArray", "bool")),
126 DbValue::Timestamp(_) => Err(DbValueError::incompatible("Timestamp", "bool")),
127 DbValue::NullString
128 | DbValue::NullInt
129 | DbValue::NullFloat
130 | DbValue::NullBool
131 | DbValue::NullBytes
132 | DbValue::NullTimestamp
133 | DbValue::NullStringArray => Err(DbValueError::null_for("bool")),
134 DbValue::Float(_) => Err(DbValueError::incompatible("Float", "bool")),
135 DbValue::Bytes(_) => Err(DbValueError::incompatible("Bytes", "bool")),
136 }
137 }
138}
139
140impl FromDbValue for Vec<u8> {
141 fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
142 match value {
143 DbValue::Bytes(b) => Ok(b.clone()),
144 DbValue::String(s) => Ok(s.as_bytes().to_vec()),
145 DbValue::NullString
146 | DbValue::NullInt
147 | DbValue::NullFloat
148 | DbValue::NullBool
149 | DbValue::NullBytes
150 | DbValue::NullTimestamp
151 | DbValue::NullStringArray => Err(DbValueError::null_for("Vec<u8>")),
152 DbValue::Int(_)
153 | DbValue::Float(_)
154 | DbValue::Bool(_)
155 | DbValue::Timestamp(_)
156 | DbValue::StringArray(_) => Err(DbValueError::incompatible("non-bytes", "Vec<u8>")),
157 }
158 }
159}
160
161impl<T: FromDbValue> FromDbValue for Option<T> {
162 fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
163 match value {
164 DbValue::NullString
165 | DbValue::NullInt
166 | DbValue::NullFloat
167 | DbValue::NullBool
168 | DbValue::NullBytes
169 | DbValue::NullTimestamp
170 | DbValue::NullStringArray => Ok(None),
171 DbValue::String(_)
172 | DbValue::Int(_)
173 | DbValue::Float(_)
174 | DbValue::Bool(_)
175 | DbValue::Bytes(_)
176 | DbValue::Timestamp(_)
177 | DbValue::StringArray(_) => T::from_db_value(value).map(Some),
178 }
179 }
180}
181
182impl FromDbValue for DateTime<Utc> {
183 fn from_db_value(value: &DbValue) -> Result<Self, DbValueError> {
184 match value {
185 DbValue::String(s) => parse_database_datetime(&serde_json::Value::String(s.clone()))
186 .ok_or_else(|| DbValueError::parse(s.clone(), "DateTime<Utc>")),
187 DbValue::Timestamp(dt) => Ok(*dt),
188 DbValue::Int(ts) => Self::from_timestamp(*ts, 0)
189 .ok_or_else(|| DbValueError::parse(ts.to_string(), "DateTime<Utc>")),
190 DbValue::NullString
191 | DbValue::NullInt
192 | DbValue::NullFloat
193 | DbValue::NullBool
194 | DbValue::NullBytes
195 | DbValue::NullTimestamp
196 | DbValue::NullStringArray => Err(DbValueError::null_for("DateTime<Utc>")),
197 DbValue::Float(_) | DbValue::Bool(_) | DbValue::Bytes(_) | DbValue::StringArray(_) => {
198 Err(DbValueError::incompatible("non-datetime", "DateTime<Utc>"))
199 },
200 }
201 }
202}