Skip to main content

qubit_value/value/
value_accessors.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9//! Strict typed accessors for scalar runtime values.
10// qubit-style: allow source-test-pair
11// Tests are intentionally distributed across behavior-specific files under
12// tests/value/ rather than collected in value_accessors_tests.rs.
13
14use std::collections::HashMap;
15use std::time::Duration;
16
17#[cfg(feature = "big-decimal")]
18use bigdecimal::BigDecimal;
19#[cfg(feature = "chrono")]
20use chrono::{
21    DateTime,
22    NaiveDate,
23    NaiveDateTime,
24    NaiveTime,
25    Utc,
26};
27#[cfg(feature = "big-integer")]
28use num_bigint::BigInt;
29#[cfg(all(feature = "converter", feature = "json"))]
30use serde::Serialize;
31#[cfg(all(feature = "converter", feature = "json"))]
32use serde::de::DeserializeOwned;
33#[cfg(feature = "url")]
34use url::Url;
35
36use qubit_datatype::DataType;
37#[cfg(all(feature = "converter", feature = "json"))]
38use qubit_datatype::{
39    DataConversionError,
40    DataFormat,
41    InvalidValueReason,
42};
43
44use super::value::{
45    Value,
46    ValueRepr,
47};
48use crate::ValueMissing;
49use crate::value_error::{
50    ValueError,
51    ValueResult,
52};
53
54macro_rules! impl_get_value {
55    // Copy type: directly dereference and return
56    ($(#[$attr:meta])* copy: $method:ident, $variant:ident, $type:ty, $data_type:expr) => {
57        $(#[$attr])*
58        #[doc = ""]
59        #[doc = "# Errors"]
60        #[doc = ""]
61        #[doc = "Returns [`ValueError::Missing`] when the value is unset with"]
62        #[doc = "the requested type, or [`ValueError::TypeMismatch`] when the"]
63        #[doc = "stored data type differs."]
64        #[inline(always)]
65        pub fn $method(&self) -> ValueResult<$type> {
66            match &self.repr {
67                ValueRepr::$variant(v) => Ok(*v),
68                ValueRepr::Unset(dt) if *dt == $data_type => {
69                    Err(ValueError::Missing($crate::ValueMissing::UnsetScalar {
70                        data_type: *dt,
71                    }))
72                }
73                ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
74                    expected: $data_type,
75                    actual: *dt,
76                }),
77                _ => Err(ValueError::TypeMismatch {
78                    expected: $data_type,
79                    actual: self.data_type(),
80                }),
81            }
82        }
83    };
84
85    // Reference type: use conversion function to return reference,
86    // fixing lifetime issues
87    ($(#[$attr:meta])* ref: $method:ident, $variant:ident, $ret_type:ty, $data_type:expr, $conversion:expr) => {
88        $(#[$attr])*
89        #[doc = ""]
90        #[doc = "# Errors"]
91        #[doc = ""]
92        #[doc = "Returns [`ValueError::Missing`] when the value is unset with"]
93        #[doc = "the requested type, or [`ValueError::TypeMismatch`] when the"]
94        #[doc = "stored data type differs."]
95        #[inline(always)]
96        pub fn $method(&self) -> ValueResult<$ret_type> {
97            match &self.repr {
98                ValueRepr::$variant(v) => {
99                    let conv_fn: fn(&_) -> $ret_type = $conversion;
100                    Ok(conv_fn(v))
101                },
102                ValueRepr::Unset(dt) if *dt == $data_type => {
103                    Err(ValueError::Missing($crate::ValueMissing::UnsetScalar {
104                        data_type: *dt,
105                    }))
106                }
107                ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
108                    expected: $data_type,
109                    actual: *dt,
110                }),
111                _ => Err(ValueError::TypeMismatch {
112                    expected: $data_type,
113                    actual: self.data_type(),
114                }),
115            }
116        }
117    };
118}
119
120impl Value {
121    /// Creates a `Value` from a `serde_json::Value`.
122    ///
123    /// # Parameters
124    ///
125    /// * `json` - The JSON value to wrap.
126    ///
127    /// # Returns
128    ///
129    /// A `Value::Json` wrapping the given JSON value.
130    #[inline(always)]
131    #[cfg(feature = "json")]
132    pub fn from_json_value(json: serde_json::Value) -> Self {
133        Value::Json(json)
134    }
135
136    /// Creates a `Value` from any serializable value by converting it to JSON.
137    ///
138    /// # Type Parameters
139    ///
140    /// * `T` - Any type implementing `Serialize`.
141    ///
142    /// # Parameters
143    ///
144    /// * `value` - The value to serialize into JSON.
145    ///
146    /// # Returns
147    ///
148    /// A `Value::Json` containing the serialized representation.
149    ///
150    /// # Errors
151    ///
152    /// Returns [`ValueError::Conversion`] with
153    /// [`InvalidValueReason::NonFinite`] when any nested float is non-finite,
154    /// or [`InvalidValueReason::Serialization`] when Serde cannot represent
155    /// the input as JSON.
156    #[cfg(all(feature = "converter", feature = "json"))]
157    pub fn from_serializable<T: ?Sized + Serialize>(
158        value: &T,
159    ) -> ValueResult<Self> {
160        let json = crate::strict_json::to_value(value).map_err(|error| {
161            let reason = match error {
162                crate::strict_json::StrictJsonError::NonFinite => {
163                    InvalidValueReason::NonFinite
164                }
165                crate::strict_json::StrictJsonError::Serialization => {
166                    InvalidValueReason::Serialization {
167                        format: DataFormat::Json,
168                    }
169                }
170            };
171            ValueError::from(DataConversionError::invalid(
172                DataType::Json,
173                DataType::Json,
174                reason,
175            ))
176        })?;
177        Ok(Value::Json(json))
178    }
179
180    // ========================================================================
181    // Type-checking getters (strict type matching)
182    // ========================================================================
183
184    impl_get_value! {
185        /// Get boolean value
186        ///
187        /// # Returns
188        ///
189        /// If types match, returns the boolean value; see `# Errors`.
190        ///
191        /// # Examples
192        ///
193        /// ```rust
194        /// use qubit_value::Value;
195        ///
196        /// let value = Value::Bool(true);
197        /// assert_eq!(value.get_bool().unwrap(), true);
198        /// ```
199        copy: get_bool, Bool, bool, DataType::Bool
200    }
201
202    impl_get_value! {
203        /// Get character value
204        ///
205        /// # Returns
206        ///
207        /// If types match, returns the character value; see `# Errors`.
208        ///
209        /// # Examples
210        ///
211        /// ```rust
212        /// use qubit_value::Value;
213        ///
214        /// let value = Value::Char('A');
215        /// assert_eq!(value.get_char().unwrap(), 'A');
216        /// ```
217        copy: get_char, Char, char, DataType::Char
218    }
219
220    impl_get_value! {
221        /// Get int8 value
222        ///
223        /// # Returns
224        ///
225        /// If types match, returns the int8 value; see `# Errors`.
226        copy: get_int8, Int8, i8, DataType::Int8
227    }
228
229    impl_get_value! {
230        /// Get int16 value
231        ///
232        /// # Returns
233        ///
234        /// If types match, returns the int16 value; see `# Errors`.
235        copy: get_int16, Int16, i16, DataType::Int16
236    }
237
238    impl_get_value! {
239        /// Get int32 value
240        ///
241        /// # Returns
242        ///
243        /// If types match, returns the int32 value; see `# Errors`.
244        copy: get_int32, Int32, i32, DataType::Int32
245    }
246
247    impl_get_value! {
248        /// Get int64 value
249        ///
250        /// # Returns
251        ///
252        /// If types match, returns the int64 value; see `# Errors`.
253        copy: get_int64, Int64, i64, DataType::Int64
254    }
255
256    impl_get_value! {
257        /// Get int128 value
258        ///
259        /// # Returns
260        ///
261        /// If types match, returns the int128 value; see `# Errors`.
262        copy: get_int128, Int128, i128, DataType::Int128
263    }
264
265    impl_get_value! {
266        /// Get uint8 value
267        ///
268        /// # Returns
269        ///
270        /// If types match, returns the uint8 value; see `# Errors`.
271        copy: get_uint8, UInt8, u8, DataType::UInt8
272    }
273
274    impl_get_value! {
275        /// Get uint16 value
276        ///
277        /// # Returns
278        ///
279        /// If types match, returns the uint16 value; see `# Errors`.
280        copy: get_uint16, UInt16, u16, DataType::UInt16
281    }
282
283    impl_get_value! {
284        /// Get uint32 value
285        ///
286        /// # Returns
287        ///
288        /// If types match, returns the uint32 value; see `# Errors`.
289        copy: get_uint32, UInt32, u32, DataType::UInt32
290    }
291
292    impl_get_value! {
293        /// Get uint64 value
294        ///
295        /// # Returns
296        ///
297        /// If types match, returns the uint64 value; see `# Errors`.
298        copy: get_uint64, UInt64, u64, DataType::UInt64
299    }
300
301    impl_get_value! {
302        /// Get uint128 value
303        ///
304        /// # Returns
305        ///
306        /// If types match, returns the uint128 value; see `# Errors`.
307        copy: get_uint128, UInt128, u128, DataType::UInt128
308    }
309
310    impl_get_value! {
311        /// Get float32 value
312        ///
313        /// # Returns
314        ///
315        /// If types match, returns the float32 value; see `# Errors`.
316        copy: get_float32, Float32, f32, DataType::Float32
317    }
318
319    impl_get_value! {
320        /// Get float64 value
321        ///
322        /// # Returns
323        ///
324        /// If types match, returns the float64 value; see `# Errors`.
325        copy: get_float64, Float64, f64, DataType::Float64
326    }
327
328    impl_get_value! {
329        /// Get string reference
330        ///
331        /// # Returns
332        ///
333        /// If types match, returns a reference to the string; see `# Errors`.
334        ///
335        /// # Examples
336        ///
337        /// ```rust
338        /// use qubit_value::Value;
339        ///
340        /// let value = Value::String("hello".to_string());
341        /// assert_eq!(value.get_string().unwrap(), "hello");
342        /// ```
343        ref: get_string, String, &str, DataType::String, |s: &String| s.as_str()
344    }
345
346    #[cfg(feature = "chrono")]
347    impl_get_value! {
348        /// Get date value
349        ///
350        /// # Returns
351        ///
352        /// If types match, returns the date value; see `# Errors`.
353        copy: get_date, Date, NaiveDate, DataType::Date
354    }
355
356    #[cfg(feature = "chrono")]
357    impl_get_value! {
358        /// Get time value
359        ///
360        /// # Returns
361        ///
362        /// If types match, returns the time value; see `# Errors`.
363        copy: get_time, Time, NaiveTime, DataType::Time
364    }
365
366    #[cfg(feature = "chrono")]
367    impl_get_value! {
368        /// Get datetime value
369        ///
370        /// # Returns
371        ///
372        /// If types match, returns the datetime value; see `# Errors`.
373        copy: get_datetime, DateTime, NaiveDateTime, DataType::DateTime
374    }
375
376    #[cfg(feature = "chrono")]
377    impl_get_value! {
378        /// Get UTC instant value
379        ///
380        /// # Returns
381        ///
382        /// If types match, returns the UTC instant value; see `# Errors`.
383        copy: get_instant, Instant, DateTime<Utc>, DataType::Instant
384    }
385
386    #[cfg(feature = "big-integer")]
387    impl_get_value! {
388        /// Get big integer value.
389        ///
390        /// This method returns a cloned [`BigInt`]. Use
391        /// [`Value::get_biginteger_ref`] to borrow the stored value without
392        /// cloning.
393        ///
394        /// # Returns
395        ///
396        /// If types match, returns the big integer value; see `# Errors`.
397        ///
398        /// # Examples
399        ///
400        /// ```rust
401        /// use qubit_value::Value;
402        /// use num_bigint::BigInt;
403        ///
404        /// let value = Value::BigInteger(BigInt::from(123456789));
405        /// assert_eq!(value.get_biginteger().unwrap(), BigInt::from(123456789));
406        /// ```
407        ref: get_biginteger, BigInteger, BigInt, DataType::BigInteger, |v: &BigInt| v.clone()
408    }
409
410    #[cfg(feature = "big-decimal")]
411    impl_get_value! {
412        /// Get big decimal value.
413        ///
414        /// This method returns a cloned [`BigDecimal`]. Use
415        /// [`Value::get_bigdecimal_ref`] to borrow the stored value without
416        /// cloning.
417        ///
418        /// # Returns
419        ///
420        /// If types match, returns the big decimal value; see `# Errors`.
421        ///
422        /// # Examples
423        ///
424        /// ```rust
425        /// use std::str::FromStr;
426        ///
427        /// use bigdecimal::BigDecimal;
428        /// use qubit_value::Value;
429        ///
430        /// let bd = BigDecimal::from_str("123.456").unwrap();
431        /// let value = Value::BigDecimal(bd.clone());
432        /// assert_eq!(value.get_bigdecimal().unwrap(), bd);
433        /// ```
434        ref: get_bigdecimal, BigDecimal, BigDecimal, DataType::BigDecimal, |v: &BigDecimal| v.clone()
435    }
436
437    impl_get_value! {
438        /// Get Duration value
439        ///
440        /// # Returns
441        ///
442        /// If types match, returns the Duration value; see `# Errors`.
443        copy: get_duration, Duration, Duration, DataType::Duration
444    }
445
446    #[cfg(feature = "url")]
447    impl_get_value! {
448        /// Get URL value.
449        ///
450        /// This method returns a cloned [`Url`]. Use [`Value::get_url_ref`] to
451        /// borrow the stored value without cloning.
452        ///
453        /// # Returns
454        ///
455        /// If types match, returns the URL value; see `# Errors`.
456        ref: get_url, Url, Url, DataType::Url, Url::clone
457    }
458
459    impl_get_value! {
460        /// Get string map value.
461        ///
462        /// This method returns a cloned `HashMap<String, String>`. Use
463        /// [`Value::get_string_map_ref`] to borrow the stored value without
464        /// cloning.
465        ///
466        /// # Returns
467        ///
468        /// If types match, returns the string map value; see `# Errors`.
469        ref: get_string_map, StringMap, HashMap<String, String>, DataType::StringMap,
470            |v: &HashMap<String, String>| v.clone()
471    }
472
473    #[cfg(feature = "json")]
474    impl_get_value! {
475        /// Get JSON value.
476        ///
477        /// This method returns a cloned [`serde_json::Value`]. Use
478        /// [`Value::get_json_ref`] to borrow the stored value without cloning.
479        ///
480        /// # Returns
481        ///
482        /// If types match, returns the JSON value; see `# Errors`.
483        ref: get_json, Json, serde_json::Value, DataType::Json,
484            |v: &serde_json::Value| v.clone()
485    }
486
487    /// Borrow the inner `BigInt` without cloning.
488    ///
489    /// # Returns
490    ///
491    /// A shared reference to the stored integer.
492    ///
493    /// # Errors
494    ///
495    /// Returns [`ValueError::Missing`] when the value is unset with
496    /// `DataType::BigInteger`, or [`ValueError::TypeMismatch`] when the stored
497    /// data type differs.
498    #[cfg(feature = "big-integer")]
499    #[inline(always)]
500    pub fn get_biginteger_ref(&self) -> ValueResult<&BigInt> {
501        match &self.repr {
502            ValueRepr::BigInteger(v) => Ok(v),
503            ValueRepr::Unset(dt) if *dt == DataType::BigInteger => {
504                Err(ValueError::Missing(ValueMissing::UnsetScalar {
505                    data_type: *dt,
506                }))
507            }
508            ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
509                expected: DataType::BigInteger,
510                actual: *dt,
511            }),
512            _ => Err(ValueError::TypeMismatch {
513                expected: DataType::BigInteger,
514                actual: self.data_type(),
515            }),
516        }
517    }
518
519    /// Borrow the inner `BigDecimal` without cloning.
520    ///
521    /// # Returns
522    ///
523    /// A shared reference to the stored decimal.
524    ///
525    /// # Errors
526    ///
527    /// Returns [`ValueError::Missing`] when the value is unset with
528    /// `DataType::BigDecimal`, or [`ValueError::TypeMismatch`] when the stored
529    /// data type differs.
530    #[cfg(feature = "big-decimal")]
531    #[inline(always)]
532    pub fn get_bigdecimal_ref(&self) -> ValueResult<&BigDecimal> {
533        match &self.repr {
534            ValueRepr::BigDecimal(v) => Ok(v),
535            ValueRepr::Unset(dt) if *dt == DataType::BigDecimal => {
536                Err(ValueError::Missing(ValueMissing::UnsetScalar {
537                    data_type: *dt,
538                }))
539            }
540            ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
541                expected: DataType::BigDecimal,
542                actual: *dt,
543            }),
544            _ => Err(ValueError::TypeMismatch {
545                expected: DataType::BigDecimal,
546                actual: self.data_type(),
547            }),
548        }
549    }
550
551    /// Borrow the inner `Url` without cloning.
552    ///
553    /// # Returns
554    ///
555    /// A shared reference to the stored URL.
556    ///
557    /// # Errors
558    ///
559    /// Returns [`ValueError::Missing`] when the value is unset with
560    /// `DataType::Url`, or [`ValueError::TypeMismatch`] when the stored data
561    /// type differs.
562    #[cfg(feature = "url")]
563    #[inline(always)]
564    pub fn get_url_ref(&self) -> ValueResult<&Url> {
565        match &self.repr {
566            ValueRepr::Url(v) => Ok(v.as_ref()),
567            ValueRepr::Unset(dt) if *dt == DataType::Url => {
568                Err(ValueError::Missing(ValueMissing::UnsetScalar {
569                    data_type: *dt,
570                }))
571            }
572            ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
573                expected: DataType::Url,
574                actual: *dt,
575            }),
576            _ => Err(ValueError::TypeMismatch {
577                expected: DataType::Url,
578                actual: self.data_type(),
579            }),
580        }
581    }
582
583    /// Borrow the inner `HashMap<String, String>` without cloning.
584    ///
585    /// # Returns
586    ///
587    /// A shared reference to the stored string map.
588    ///
589    /// # Errors
590    ///
591    /// Returns [`ValueError::Missing`] when the value is unset with
592    /// `DataType::StringMap`, or [`ValueError::TypeMismatch`] when the stored
593    /// data type differs.
594    #[inline(always)]
595    pub fn get_string_map_ref(&self) -> ValueResult<&HashMap<String, String>> {
596        match &self.repr {
597            ValueRepr::StringMap(v) => Ok(v),
598            ValueRepr::Unset(dt) if *dt == DataType::StringMap => {
599                Err(ValueError::Missing(ValueMissing::UnsetScalar {
600                    data_type: *dt,
601                }))
602            }
603            ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
604                expected: DataType::StringMap,
605                actual: *dt,
606            }),
607            _ => Err(ValueError::TypeMismatch {
608                expected: DataType::StringMap,
609                actual: self.data_type(),
610            }),
611        }
612    }
613
614    /// Borrow the inner JSON value without cloning.
615    ///
616    /// # Returns
617    ///
618    /// A shared reference to the stored JSON value.
619    ///
620    /// # Errors
621    ///
622    /// Returns [`ValueError::Missing`] when the value is unset with
623    /// `DataType::Json`, or [`ValueError::TypeMismatch`] when the stored data
624    /// type differs.
625    #[cfg(feature = "json")]
626    #[inline(always)]
627    pub fn get_json_ref(&self) -> ValueResult<&serde_json::Value> {
628        match &self.repr {
629            ValueRepr::Json(v) => Ok(v),
630            ValueRepr::Unset(dt) if *dt == DataType::Json => {
631                Err(ValueError::Missing(ValueMissing::UnsetScalar {
632                    data_type: *dt,
633                }))
634            }
635            ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
636                expected: DataType::Json,
637                actual: *dt,
638            }),
639            _ => Err(ValueError::TypeMismatch {
640                expected: DataType::Json,
641                actual: self.data_type(),
642            }),
643        }
644    }
645
646    /// Deserialize the inner JSON value into a target type.
647    ///
648    /// Only works when `self` is `Value::Json(...)`.
649    ///
650    /// # Type Parameters
651    ///
652    /// * `T` - The target type implementing `DeserializeOwned`.
653    ///
654    /// # Returns
655    ///
656    /// Returns `Ok(T)` on success.
657    ///
658    /// # Errors
659    ///
660    /// Returns [`ValueError::Missing`] when this value is
661    /// `Value::Unset(DataType::Json)`,
662    /// [`ValueError::TypeMismatch`] when this value has a non-JSON data type,
663    /// or [`ValueError::Conversion`] when JSON deserialization fails.
664    #[cfg(all(feature = "converter", feature = "json"))]
665    pub fn deserialize_json<T: DeserializeOwned>(&self) -> ValueResult<T> {
666        match &self.repr {
667            ValueRepr::Json(v) => {
668                serde::Deserialize::deserialize(v).map_err(|_| {
669                    ValueError::from(DataConversionError::invalid(
670                        DataType::Json,
671                        DataType::Json,
672                        InvalidValueReason::Deserialization {
673                            format: DataFormat::Json,
674                        },
675                    ))
676                })
677            }
678            ValueRepr::Unset(dt) if *dt == DataType::Json => {
679                Err(ValueError::Missing(ValueMissing::UnsetScalar {
680                    data_type: *dt,
681                }))
682            }
683            ValueRepr::Unset(dt) => Err(ValueError::TypeMismatch {
684                expected: DataType::Json,
685                actual: *dt,
686            }),
687            _ => Err(ValueError::TypeMismatch {
688                expected: DataType::Json,
689                actual: self.data_type(),
690            }),
691        }
692    }
693}