xapi_rs/data/
ci_string.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3//! A String Type wrapper (using the `unicase` crate) that ignores case when
4//! comparing strings.
5
6use crate::data::Fingerprint;
7use core::fmt;
8use serde::{
9    de::{self, Visitor},
10    Deserialize, Deserializer, Serialize, Serializer,
11};
12use std::{
13    cmp::Ordering,
14    hash::{Hash, Hasher},
15    ops::Deref,
16};
17use unicase::UniCase;
18
19/// A Type that effectively wraps a [UniCase] type to allow + facilitate
20/// using case-insensitive strings including serializing and deserializing
21/// them to/from JSON.
22#[derive(Clone, Debug, Deserialize, Eq, Serialize)]
23pub struct CIString(
24    #[serde(serialize_with = "unicase_ser", deserialize_with = "unicase_des")] UniCase<String>,
25);
26
27impl CIString {
28    /// Constructor from a `String` or an `&str` instance.
29    pub(crate) fn from<S: AsRef<str>>(s: S) -> Self {
30        CIString(UniCase::from(s.as_ref()))
31    }
32
33    /// A pass-through method to the wrapped [UniCase] instance.
34    pub(crate) fn is_empty(&self) -> bool {
35        self.0.is_empty()
36    }
37}
38
39impl PartialEq for CIString {
40    fn eq(&self, other: &Self) -> bool {
41        self.0 == other.0
42    }
43}
44
45impl PartialEq<String> for CIString {
46    fn eq(&self, other: &String) -> bool {
47        self.0.as_str() == other
48    }
49}
50
51impl PartialEq<str> for CIString {
52    fn eq(&self, other: &str) -> bool {
53        self.0.as_str() == other
54    }
55}
56
57impl Hash for CIString {
58    fn hash<H: Hasher>(&self, state: &mut H) {
59        self.0.hash(state);
60    }
61}
62
63impl Ord for CIString {
64    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
65        self.0.cmp(&other.0)
66    }
67}
68
69impl PartialOrd for CIString {
70    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
71        Some(self.cmp(other))
72    }
73}
74
75impl PartialOrd<str> for CIString {
76    fn partial_cmp(&self, other: &str) -> Option<Ordering> {
77        self.0.as_str().partial_cmp(other)
78    }
79}
80
81impl fmt::Display for CIString {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}", self.0)
84    }
85}
86
87impl Deref for CIString {
88    type Target = str;
89
90    fn deref(&self) -> &Self::Target {
91        self.0.as_str()
92    }
93}
94
95impl Fingerprint for CIString {
96    fn fingerprint<H: Hasher>(&self, state: &mut H) {
97        self.0.as_str().to_lowercase().hash(state);
98    }
99}
100
101/// JSON deserialization visitor for correctly parsing case insensitive strings.
102struct UnicaseVisitor;
103
104impl Visitor<'_> for UnicaseVisitor {
105    type Value = UniCase<String>;
106
107    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
108        formatter.write_str("a case-insensitive string")
109    }
110
111    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
112    where
113        E: de::Error,
114    {
115        Ok(UniCase::new(value.to_owned()))
116    }
117}
118
119/// Serializer implementation for the wrapped Unicase string.
120fn unicase_ser<S>(this: &UniCase<String>, ser: S) -> Result<S::Ok, S::Error>
121where
122    S: Serializer,
123{
124    ser.serialize_str(this.as_str())
125}
126
127/// Deserializer implementation for the wrapped Unicase string.
128fn unicase_des<'de, D>(des: D) -> Result<UniCase<String>, D::Error>
129where
130    D: Deserializer<'de>,
131{
132    des.deserialize_str(UnicaseVisitor)
133}