Skip to main content

mpl_lang/
tags.rs

1//! Series tags and tag values
2use 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/// Value for a tag k/v pair
13#[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 value
19    Null,
20    /// Boolean value
21    Bool(bool),
22    /// Integer value
23    Int(i64),
24    /// Float value
25    Float(f64),
26    /// String value
27    String(#[cfg_attr(feature = "bincode", bincode(with_serde))] SharedString),
28    /// Array value
29    Array(Vec<TagValue>),
30}
31impl TagValue {
32    /// Returns the type of the tag value.
33    #[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                // Since arguments could include PII we do replace them with a hash
55                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            // First the easy cases, if we have two values of the same type,
70            // compare them directly
71            (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            // If we have two numeric values of different types,
79            // cast them to f64 for and compare
80            (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            // This are now in reverse order of precedence
92            // the rule we use is 'the more complex the type is the
93            // greater the ordering'
94
95            // Everything greater than Null
96            (TagValue::Null, _) => std::cmp::Ordering::Less,
97            (_, TagValue::Null) => std::cmp::Ordering::Greater,
98
99            // The rest if larger than bool
100            (TagValue::Bool(_), _) => std::cmp::Ordering::Less,
101            (_, TagValue::Bool(_)) => std::cmp::Ordering::Greater,
102
103            // now everything else is larger than int
104            (TagValue::Int(_), _) => std::cmp::Ordering::Less,
105            (_, TagValue::Int(_)) => std::cmp::Ordering::Greater,
106
107            // now everything else is larger than float
108            (TagValue::Float(_), _) => std::cmp::Ordering::Less,
109            (_, TagValue::Float(_)) => std::cmp::Ordering::Greater,
110            // now everything else is larger than string
111            (TagValue::String(_), _) => std::cmp::Ordering::Less,
112            (_, TagValue::String(_)) => std::cmp::Ordering::Greater,
113            // string is the largest type - this is a unreachable case
114            // as the prior matches already handle this.
115            // (TagValue::String(_), _) => std::cmp::Ordering::Less,
116            // (_, TagValue::String(_)) => std::cmp::Ordering::Greater,
117        }
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    /// Tries to access the tag value as a string
127    #[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    /// Returns the length of the tag value in estimated bytes
137    #[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, // size of i64 or f64
144            TagValue::Bool(_) => 1,                     // size of bool
145        }
146    }
147    /// Returns true if the tag value is empty
148    #[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, // bool, i64 and f64 are never empty
155        }
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
173// FIXME! This is not good since we have floats
174impl 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}