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}