1use std::{
3 fmt,
4 hash::{DefaultHasher, Hash, Hasher},
5};
6
7use ordered_float::OrderedFloat;
8use strumbra::SharedString;
9
10use crate::{query::TagType, types::StrumbraError};
11
12#[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize, Default)]
14#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
15#[serde(untagged)]
16#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
17#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
18pub enum TagValue {
19 #[default]
20 None,
22 Bool(bool),
24 Int(i64),
26 Float(f64),
28 String(
30 #[cfg_attr(feature = "wasm", tsify(type = "String"))]
31 #[cfg_attr(feature = "bincode", bincode(with_serde))]
32 SharedString,
33 ),
34}
35impl TagValue {
36 #[must_use]
38 pub fn tpe(&self) -> TagType {
39 match self {
40 Self::None => TagType::None,
41 Self::Bool(_) => TagType::Bool,
42 Self::Int(_) => TagType::Int,
43 Self::Float(_) => TagType::Float,
44 Self::String(_) => TagType::String,
45 }
46 }
47}
48
49impl fmt::Debug for TagValue {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::None => write!(f, "None"),
53 Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
54 Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
55 Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
56 Self::String(arg0) => {
57 let mut hasher = DefaultHasher::new();
59 arg0.hash(&mut hasher);
60 f.debug_tuple("PiiSafeString")
61 .field(&hasher.finish())
62 .finish()
63 }
64 }
65 }
66}
67
68impl Ord for TagValue {
69 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
70 match (self, other) {
71 (TagValue::None, TagValue::None) => std::cmp::Ordering::Equal,
74 (TagValue::Int(a), TagValue::Int(b)) => a.cmp(b),
75 (TagValue::Float(a), TagValue::Float(b)) => OrderedFloat(*a).cmp(&OrderedFloat(*b)),
76 (TagValue::String(a), TagValue::String(b)) => a.cmp(b),
77 (TagValue::Bool(a), TagValue::Bool(b)) => a.cmp(b),
78
79 (TagValue::Int(i), TagValue::Float(f)) =>
82 {
83 #[allow(clippy::cast_precision_loss)]
84 OrderedFloat(*i as f64).cmp(&OrderedFloat(*f))
85 }
86 (TagValue::Float(f), TagValue::Int(i)) =>
87 {
88 #[allow(clippy::cast_precision_loss)]
89 OrderedFloat(*f).cmp(&OrderedFloat(*i as f64))
90 }
91
92 (TagValue::None, _) => std::cmp::Ordering::Less,
98 (_, TagValue::None) => std::cmp::Ordering::Greater,
99
100 (TagValue::Bool(_), _) => std::cmp::Ordering::Less,
102 (_, TagValue::Bool(_)) => std::cmp::Ordering::Greater,
103
104 (TagValue::Int(_), _) => std::cmp::Ordering::Less,
106 (_, TagValue::Int(_)) => std::cmp::Ordering::Greater,
107
108 (TagValue::Float(_), _) => std::cmp::Ordering::Less,
110 (_, TagValue::Float(_)) => std::cmp::Ordering::Greater,
111 }
116 }
117}
118impl PartialOrd for TagValue {
119 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
120 Some(self.cmp(other))
121 }
122}
123impl TagValue {
124 #[must_use]
126 pub fn as_str(&self) -> Option<&str> {
127 if let TagValue::String(s) = self {
128 Some(s.as_str())
129 } else {
130 None
131 }
132 }
133
134 #[must_use]
136 pub fn len(&self) -> usize {
137 match self {
138 TagValue::None => 0,
139 TagValue::String(s) => s.len(),
140 TagValue::Int(_) | TagValue::Float(_) => 8, TagValue::Bool(_) => 1, }
143 }
144 #[must_use]
146 pub fn is_empty(&self) -> bool {
147 match self {
148 TagValue::None => true,
149 TagValue::String(s) => s.is_empty(),
150 TagValue::Bool(_) | TagValue::Int(_) | TagValue::Float(_) => false, }
152 }
153}
154
155impl Hash for TagValue {
156 fn hash<H: Hasher>(&self, state: &mut H) {
157 core::mem::discriminant(self).hash(state);
158 match self {
159 TagValue::None => (),
160 TagValue::String(s) => s.hash(state),
161 TagValue::Int(i) => i.hash(state),
162 TagValue::Float(fl) => OrderedFloat(*fl).hash(state),
163 TagValue::Bool(b) => b.hash(state),
164 }
165 }
166}
167
168impl Eq for TagValue {}
170
171impl std::fmt::Display for TagValue {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 TagValue::None => write!(f, "None"),
175 TagValue::String(s) => {
176 let mut hasher = DefaultHasher::new();
177 s.hash(&mut hasher);
178
179 write!(f, "\"<PII Safe String: {}>\"", &hasher.finish())
180 }
181 TagValue::Int(i) => write!(f, "{i}"),
182 TagValue::Float(fl) => write!(f, "{fl}"),
183 TagValue::Bool(b) => write!(f, "{b}"),
184 }
185 }
186}
187
188impl From<i64> for TagValue {
189 fn from(i: i64) -> Self {
190 TagValue::Int(i)
191 }
192}
193
194impl From<f64> for TagValue {
195 fn from(f: f64) -> Self {
196 TagValue::Float(f)
197 }
198}
199
200impl From<bool> for TagValue {
201 fn from(b: bool) -> Self {
202 TagValue::Bool(b)
203 }
204}
205impl TryFrom<String> for TagValue {
206 type Error = StrumbraError;
207 fn try_from(s: String) -> Result<Self, Self::Error> {
208 Ok(TagValue::String(SharedString::try_from(s)?))
209 }
210}
211impl TryFrom<&str> for TagValue {
212 type Error = StrumbraError;
213 fn try_from(s: &str) -> Result<Self, Self::Error> {
214 Ok(TagValue::String(SharedString::try_from(s)?))
215 }
216}