Skip to main content

qubit_validator/binding/
validation_value.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//! Borrowed values passed across the erased validation boundary.
10
11use std::any::Any;
12use std::fmt::Debug;
13use std::fmt::Formatter;
14use std::fmt::Result;
15
16use super::InputType;
17
18/// A value view that deliberately avoids implicit stringification and cloning.
19///
20/// # Type Parameters
21///
22/// - `'a`: Lifetime for which the borrowed input remains available.
23///
24/// # Examples
25///
26/// ```
27/// use qubit_validator::ValidationValue;
28///
29/// let value = ValidationValue::Text("hello");
30/// assert_eq!(value.as_text(), Some("hello"));
31/// assert!(!value.is_missing());
32/// ```
33#[derive(Clone, Copy)]
34#[non_exhaustive]
35pub enum ValidationValue<'a> {
36    /// A borrowed UTF-8 string slice.
37    Text(
38        /// Raw text borrowed only for the duration of validation.
39        &'a str,
40    ),
41    /// A borrowed value with an exact concrete type available through `Any`.
42    Typed(
43        /// Raw typed value borrowed only for the duration of validation.
44        &'a dyn Any,
45    ),
46    /// An explicitly expanded optional value which is absent.
47    Missing,
48}
49
50impl<'a> ValidationValue<'a> {
51    /// Returns the concrete input shape, if one is present.
52    ///
53    /// # Returns
54    ///
55    /// Returns `None` only for [`Self::Missing`].
56    #[inline]
57    pub fn input_type(self) -> Option<InputType> {
58        match self {
59            Self::Text(_) => Some(InputType::Text),
60            Self::Typed(value) => Some(InputType::Typed(value.type_id())),
61            Self::Missing => None,
62        }
63    }
64
65    /// Returns the text view when this is a text value.
66    ///
67    /// # Returns
68    ///
69    /// Returns `None` for typed and missing values.
70    #[inline]
71    pub const fn as_text(self) -> Option<&'a str> {
72        match self {
73            Self::Text(value) => Some(value),
74            _ => None,
75        }
76    }
77
78    /// Returns whether this value is the explicit missing marker.
79    ///
80    /// # Returns
81    ///
82    /// Returns `true` only for [`Self::Missing`].
83    #[must_use]
84    #[inline]
85    pub const fn is_missing(self) -> bool {
86        matches!(self, Self::Missing)
87    }
88
89    /// Attempts to borrow the value as its exact concrete type.
90    ///
91    /// # Type Parameters
92    ///
93    /// - `T`: Requested concrete type, which must be `'static` for `Any`.
94    ///
95    /// # Returns
96    ///
97    /// Returns `None` for text, missing, and differently typed values.
98    #[inline]
99    pub fn typed<T: 'static>(self) -> Option<&'a T> {
100        match self {
101            Self::Typed(value) => value.downcast_ref::<T>(),
102            _ => None,
103        }
104    }
105}
106
107impl Debug for ValidationValue<'_> {
108    /// Formats only the value shape and always redacts raw input.
109    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
110        match self {
111            Self::Text(_) => formatter.write_str("Text(<redacted>)"),
112            Self::Typed(_) => formatter.write_str("Typed(<redacted>)"),
113            Self::Missing => formatter.write_str("Missing"),
114        }
115    }
116}