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)]
16pub enum TagValue {
17 #[default]
18 Null,
20 Bool(bool),
22 Int(i64),
24 Float(f64),
26 String(#[cfg_attr(feature = "bincode", bincode(with_serde))] SharedString),
28 Array(Vec<TagValue>),
30}
31impl TagValue {
32 #[must_use]
34 pub fn tpe(&self) -> TagType {
35 match self {
36 Self::Null => TagType::Null,
37 Self::Bool(_) => TagType::Bool,
38 Self::Int(_) => TagType::Int,
39 Self::Float(_) => TagType::Float,
40 Self::String(_) => TagType::String,
41 Self::Array(_) => TagType::Array,
42 }
43 }
44}
45
46impl fmt::Debug for TagValue {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::Null => write!(f, "Null"),
50 Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
51 Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
52 Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
53 Self::String(arg0) => {
54 let mut hasher = DefaultHasher::new();
56 arg0.hash(&mut hasher);
57 f.debug_tuple("PiiSafeString")
58 .field(&hasher.finish())
59 .finish()
60 }
61 Self::Array(arg0) => f.debug_tuple("Array").field(arg0).finish(),
62 }
63 }
64}
65
66impl Ord for TagValue {
67 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
68 match (self, other) {
69 (TagValue::Null, TagValue::Null) => std::cmp::Ordering::Equal,
72 (TagValue::Int(a), TagValue::Int(b)) => a.cmp(b),
73 (TagValue::Float(a), TagValue::Float(b)) => OrderedFloat(*a).cmp(&OrderedFloat(*b)),
74 (TagValue::String(a), TagValue::String(b)) => a.cmp(b),
75 (TagValue::Bool(a), TagValue::Bool(b)) => a.cmp(b),
76 (TagValue::Array(a), TagValue::Array(b)) => a.cmp(b),
77
78 (TagValue::Int(i), TagValue::Float(f)) =>
81 {
82 #[allow(clippy::cast_precision_loss)]
83 OrderedFloat(*i as f64).cmp(&OrderedFloat(*f))
84 }
85 (TagValue::Float(f), TagValue::Int(i)) =>
86 {
87 #[allow(clippy::cast_precision_loss)]
88 OrderedFloat(*f).cmp(&OrderedFloat(*i as f64))
89 }
90
91 (TagValue::Null, _) => std::cmp::Ordering::Less,
97 (_, TagValue::Null) => std::cmp::Ordering::Greater,
98
99 (TagValue::Bool(_), _) => std::cmp::Ordering::Less,
101 (_, TagValue::Bool(_)) => std::cmp::Ordering::Greater,
102
103 (TagValue::Int(_), _) => std::cmp::Ordering::Less,
105 (_, TagValue::Int(_)) => std::cmp::Ordering::Greater,
106
107 (TagValue::Float(_), _) => std::cmp::Ordering::Less,
109 (_, TagValue::Float(_)) => std::cmp::Ordering::Greater,
110 (TagValue::String(_), _) => std::cmp::Ordering::Less,
112 (_, TagValue::String(_)) => std::cmp::Ordering::Greater,
113 }
118 }
119}
120impl PartialOrd for TagValue {
121 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
122 Some(self.cmp(other))
123 }
124}
125impl TagValue {
126 #[must_use]
128 pub fn as_str(&self) -> Option<&str> {
129 if let TagValue::String(s) = self {
130 Some(s.as_str())
131 } else {
132 None
133 }
134 }
135
136 #[must_use]
138 pub fn len(&self) -> usize {
139 match self {
140 TagValue::Null => 0,
141 TagValue::String(s) => s.len(),
142 TagValue::Array(a) => a.iter().map(TagValue::len).sum(),
143 TagValue::Int(_) | TagValue::Float(_) => 8, TagValue::Bool(_) => 1, }
146 }
147 #[must_use]
149 pub fn is_empty(&self) -> bool {
150 match self {
151 TagValue::Null => true,
152 TagValue::String(s) => s.is_empty(),
153 TagValue::Array(a) => a.is_empty(),
154 TagValue::Bool(_) | TagValue::Int(_) | TagValue::Float(_) => false, }
156 }
157}
158
159impl Hash for TagValue {
160 fn hash<H: Hasher>(&self, state: &mut H) {
161 core::mem::discriminant(self).hash(state);
162 match self {
163 TagValue::Null => (),
164 TagValue::String(s) => s.hash(state),
165 TagValue::Int(i) => i.hash(state),
166 TagValue::Float(fl) => OrderedFloat(*fl).hash(state),
167 TagValue::Bool(b) => b.hash(state),
168 TagValue::Array(a) => a.hash(state),
169 }
170 }
171}
172
173impl Eq for TagValue {}
175
176impl std::fmt::Display for TagValue {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 match self {
179 TagValue::Null => write!(f, "Null"),
180 TagValue::String(s) => {
181 let mut hasher = DefaultHasher::new();
182 s.hash(&mut hasher);
183
184 write!(f, "\"<PII Safe String: {}>\"", hasher.finish())
185 }
186 TagValue::Int(i) => write!(f, "{i}"),
187 TagValue::Float(fl) => write!(f, "{fl}"),
188 TagValue::Bool(b) => write!(f, "{b}"),
189 TagValue::Array(a) => {
190 write!(f, "[")?;
191 for (i, item) in a.iter().enumerate() {
192 item.fmt(f)?;
193 if i < a.len() - 1 {
194 write!(f, ", ")?;
195 }
196 }
197 write!(f, "]")
198 }
199 }
200 }
201}
202
203impl From<i64> for TagValue {
204 fn from(i: i64) -> Self {
205 TagValue::Int(i)
206 }
207}
208
209impl From<f64> for TagValue {
210 fn from(f: f64) -> Self {
211 TagValue::Float(f)
212 }
213}
214
215impl From<bool> for TagValue {
216 fn from(b: bool) -> Self {
217 TagValue::Bool(b)
218 }
219}
220impl TryFrom<String> for TagValue {
221 type Error = StrumbraError;
222 fn try_from(s: String) -> Result<Self, Self::Error> {
223 Ok(TagValue::String(SharedString::try_from(s)?))
224 }
225}
226impl TryFrom<&str> for TagValue {
227 type Error = StrumbraError;
228 fn try_from(s: &str) -> Result<Self, Self::Error> {
229 Ok(TagValue::String(SharedString::try_from(s)?))
230 }
231}