Skip to main content

radixdb_api/
value.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Conversion from the neutral storage value to public Rust API types.
16
17use radixdb_core::{DataType, Error, Result, Value};
18
19use crate::params::DecimalValue;
20
21/// Trait for converting from [`Value`] to a Rust type.
22pub trait FromValue: Sized {
23    /// Convert a value to `Self`.
24    fn from_value(value: &Value) -> Result<Self>;
25}
26
27impl FromValue for i64 {
28    fn from_value(value: &Value) -> Result<Self> {
29        match value {
30            Value::Integer(i) => Ok(*i),
31            Value::Float(f)
32                if f.is_finite() && *f >= i64::MIN as f64 && *f < -(i64::MIN as f64) =>
33            {
34                let integer = *f as i64;
35                (Value::Integer(integer) == Value::Float(*f))
36                    .then_some(integer)
37                    .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "Integer"))
38            }
39            _ => Err(Error::TypeConversion {
40                from: format!("{:?}", value),
41                to: "Integer".to_string(),
42            }),
43        }
44    }
45}
46
47impl FromValue for i32 {
48    fn from_value(value: &Value) -> Result<Self> {
49        let integer = i64::from_value(value)?;
50        i32::try_from(integer).map_err(|_| Error::type_conversion(format!("{value:?}"), "Integer"))
51    }
52}
53
54impl FromValue for f64 {
55    fn from_value(value: &Value) -> Result<Self> {
56        let candidate = match value {
57            Value::Float(f) => return Ok(*f),
58            Value::Integer(i) => *i as f64,
59            // DECIMAL remains exact in storage and on the wire. Converting it
60            // here is an explicit caller choice (`row.get::<f64>()`) and may
61            // lose precision, just like converting an integer wider than the
62            // exact f64 mantissa range.
63            Value::Extension(data) if data.first() == Some(&(DataType::Decimal as u8)) => value
64                .as_decimal_parts()
65                .and_then(|(unscaled, _, scale)| {
66                    radixdb_core::value::format_decimal_parts(unscaled, scale)
67                        .parse::<f64>()
68                        .ok()
69                })
70                .ok_or_else(|| Error::TypeConversion {
71                    from: format!("{:?}", value),
72                    to: "Float".to_string(),
73                })?,
74            _ => return Err(Error::type_conversion(format!("{value:?}"), "Float")),
75        };
76        if Value::Float(candidate) == *value {
77            Ok(candidate)
78        } else {
79            Err(Error::type_conversion(format!("{value:?}"), "Float"))
80        }
81    }
82}
83
84impl FromValue for String {
85    fn from_value(value: &Value) -> Result<Self> {
86        match value {
87            Value::Text(s) => Ok(s.to_string()),
88            Value::Extension(data) if data.first() == Some(&(DataType::Json as u8)) => {
89                std::str::from_utf8(&data[1..])
90                    .map(str::to_owned)
91                    .map_err(|_| Error::type_conversion(format!("{value:?}"), "String"))
92            }
93            Value::Integer(i) => Ok(i.to_string()),
94            Value::Float(f) => Ok(f.to_string()),
95            Value::Boolean(b) => Ok(if *b {
96                "true".to_string()
97            } else {
98                "false".to_string()
99            }),
100            Value::Timestamp(ts) => Ok(ts.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)),
101            Value::Extension(_) => value
102                .as_string()
103                .ok_or_else(|| Error::invalid_argument("Cannot convert extension to String")),
104            Value::Null(_) => Err(Error::type_conversion("NULL", "String")),
105        }
106    }
107}
108
109impl FromValue for bool {
110    fn from_value(value: &Value) -> Result<Self> {
111        match value {
112            Value::Boolean(b) => Ok(*b),
113            Value::Integer(i) => Ok(*i != 0),
114            _ => Err(Error::TypeConversion {
115                from: format!("{:?}", value),
116                to: "Boolean".to_string(),
117            }),
118        }
119    }
120}
121
122impl FromValue for Value {
123    fn from_value(value: &Value) -> Result<Self> {
124        Ok(value.clone())
125    }
126}
127
128impl FromValue for chrono::DateTime<chrono::Utc> {
129    fn from_value(value: &Value) -> Result<Self> {
130        match value {
131            Value::Timestamp(timestamp) => Ok(*timestamp),
132            _ => Err(Error::type_conversion(format!("{value:?}"), "Timestamp")),
133        }
134    }
135}
136
137impl FromValue for chrono::NaiveDate {
138    fn from_value(value: &Value) -> Result<Self> {
139        let days = value
140            .as_date_days()
141            .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "Date"))?;
142        chrono::NaiveDate::from_ymd_opt(1970, 1, 1)
143            .expect("valid Unix epoch date")
144            .checked_add_signed(chrono::Duration::days(i64::from(days)))
145            .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "Date"))
146    }
147}
148
149impl FromValue for Vec<u8> {
150    fn from_value(value: &Value) -> Result<Self> {
151        value
152            .as_bytes_value()
153            .map(ToOwned::to_owned)
154            .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "Bytes"))
155    }
156}
157
158impl FromValue for Vec<f32> {
159    fn from_value(value: &Value) -> Result<Self> {
160        value
161            .as_vector_f32()
162            .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "Vector"))
163    }
164}
165
166impl FromValue for uuid::Uuid {
167    fn from_value(value: &Value) -> Result<Self> {
168        value
169            .as_uuid_bytes()
170            .map(uuid::Uuid::from_bytes)
171            .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "UUID"))
172    }
173}
174
175impl FromValue for serde_json::Value {
176    fn from_value(value: &Value) -> Result<Self> {
177        let json = value
178            .as_json()
179            .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "JSON"))?;
180        serde_json::from_str(json).map_err(|_| Error::type_conversion(format!("{value:?}"), "JSON"))
181    }
182}
183
184impl FromValue for DecimalValue {
185    fn from_value(value: &Value) -> Result<Self> {
186        let (unscaled, precision, scale) = value
187            .as_decimal_parts()
188            .ok_or_else(|| Error::type_conversion(format!("{value:?}"), "Decimal"))?;
189        DecimalValue::try_new(unscaled, precision, scale)
190    }
191}
192
193impl<T: FromValue> FromValue for Option<T> {
194    fn from_value(value: &Value) -> Result<Self> {
195        if value.is_null() {
196            Ok(None)
197        } else {
198            Ok(Some(T::from_value(value)?))
199        }
200    }
201}