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}