mongodb/
collation.rs

1use std::convert::TryFrom;
2
3use serde::{Deserialize, Serialize};
4use typed_builder::TypedBuilder;
5
6use crate::error::{Error, ErrorKind};
7
8/// A collation configuration. See the official MongoDB
9/// [documentation](https://www.mongodb.com/docs/manual/reference/collation/) for more information on
10/// each of the fields.
11#[serde_with::skip_serializing_none]
12#[derive(Clone, Debug, Default, Serialize, Deserialize, TypedBuilder)]
13#[serde(rename_all = "camelCase")]
14#[builder(field_defaults(default, setter(into)))]
15#[non_exhaustive]
16pub struct Collation {
17    /// The ICU locale.
18    ///
19    /// See the list of supported languages and locales [here](https://www.mongodb.com/docs/manual/reference/collation-locales-defaults/#collation-languages-locales).
20    #[builder(!default)]
21    pub locale: String,
22
23    /// The level of comparison to perform. Corresponds to [ICU Comparison Levels](http://userguide.icu-project.org/collation/concepts#TOC-Comparison-Levels).
24    pub strength: Option<CollationStrength>,
25
26    /// Whether to include a separate level for case differences. See [ICU Collation: CaseLevel](http://userguide.icu-project.org/collation/concepts#TOC-CaseLevel) for more information.
27    pub case_level: Option<bool>,
28
29    /// The sort order of case differences during tertiary level comparisons.
30    pub case_first: Option<CollationCaseFirst>,
31
32    /// Whether to compare numeric strings as numbers or strings.
33    pub numeric_ordering: Option<bool>,
34
35    /// Whether collation should consider whitespace and punctuation as base characters for
36    /// purposes of comparison.
37    pub alternate: Option<CollationAlternate>,
38
39    /// Up to which characters are considered ignorable when `alternate` is "shifted". Has no
40    /// effect if `alternate` is set to "non-ignorable".
41    pub max_variable: Option<CollationMaxVariable>,
42
43    /// Whether to check if text require normalization and to perform it.
44    pub normalization: Option<bool>,
45
46    /// Whether strings with diacritics sort from the back of the string.
47    pub backwards: Option<bool>,
48}
49
50/// The level of comparison to perform. Corresponds to [ICU Comparison Levels](http://userguide.icu-project.org/collation/concepts#TOC-Comparison-Levels).
51#[derive(Debug, Clone, Copy)]
52#[non_exhaustive]
53pub enum CollationStrength {
54    /// Typically, this is used to denote differences between base characters (for example, "a" <
55    /// "b").
56    ///
57    /// This is also called the level-1 strength.
58    Primary,
59
60    /// Accents in the characters are considered secondary differences (for example, "as" < "às" <
61    /// "at").
62    ///
63    /// This is also called the level-2 strength.
64    Secondary,
65
66    /// Upper and lower case differences in characters are distinguished at the tertiary level (for
67    /// example, "ao" < "Ao" < "aò").
68    ///
69    /// This is also called the level-3 strength.
70    Tertiary,
71
72    /// When punctuation is ignored at level 1-3, an additional level can be used to distinguish
73    /// words with and without punctuation (for example, "ab" < "a-b" < "aB").
74    ///
75    /// This is also called the level-4 strength.
76    Quaternary,
77
78    /// When all other levels are equal, the identical level is used as a tiebreaker. The Unicode
79    /// code point values of the NFD form of each string are compared at this level, just in
80    /// case there is no difference at levels 1-4.
81    ///
82    /// This is also called the level-5 strength.
83    Identical,
84}
85
86impl From<CollationStrength> for u32 {
87    fn from(strength: CollationStrength) -> Self {
88        match strength {
89            CollationStrength::Primary => 1,
90            CollationStrength::Secondary => 2,
91            CollationStrength::Tertiary => 3,
92            CollationStrength::Quaternary => 4,
93            CollationStrength::Identical => 5,
94        }
95    }
96}
97
98impl TryFrom<u32> for CollationStrength {
99    type Error = Error;
100
101    fn try_from(level: u32) -> Result<Self, Self::Error> {
102        Ok(match level {
103            1 => CollationStrength::Primary,
104            2 => CollationStrength::Secondary,
105            3 => CollationStrength::Tertiary,
106            4 => CollationStrength::Quaternary,
107            5 => CollationStrength::Identical,
108            _ => {
109                return Err(ErrorKind::InvalidArgument {
110                    message: (format!("invalid collation strength: {}", level)),
111                }
112                .into())
113            }
114        })
115    }
116}
117
118impl Serialize for CollationStrength {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: serde::Serializer,
122    {
123        let level = u32::from(*self);
124        serializer.serialize_i32(level.try_into().map_err(serde::ser::Error::custom)?)
125    }
126}
127
128impl<'de> Deserialize<'de> for CollationStrength {
129    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
130    where
131        D: serde::Deserializer<'de>,
132    {
133        let level = u32::deserialize(deserializer)?;
134        Self::try_from(level).map_err(serde::de::Error::custom)
135    }
136}
137
138impl std::fmt::Display for CollationStrength {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        std::fmt::Display::fmt(&u32::from(*self), f)
141    }
142}
143
144/// Setting that determines sort order of case differences during case tertiary level comparisons.
145/// For more info, see <http://userguide.icu-project.org/collation/customization>.
146#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
147#[serde(rename_all = "kebab-case")]
148#[non_exhaustive]
149pub enum CollationCaseFirst {
150    /// Uppercase sorts before lowercase.
151    Upper,
152
153    /// Lowercase sorts before uppercase.
154    Lower,
155
156    /// Default value. Similar to `Lower` with slight differences.
157    /// See <http://userguide.icu-project.org/collation/customization> for details of differences.
158    Off,
159}
160
161impl std::str::FromStr for CollationCaseFirst {
162    type Err = Error;
163
164    fn from_str(s: &str) -> Result<Self, Self::Err> {
165        match s {
166            "upper" => Ok(CollationCaseFirst::Upper),
167            "lower" => Ok(CollationCaseFirst::Lower),
168            "off" => Ok(CollationCaseFirst::Off),
169            _ => Err(ErrorKind::InvalidArgument {
170                message: format!("invalid CollationCaseFirst: {}", s),
171            }
172            .into()),
173        }
174    }
175}
176
177impl CollationCaseFirst {
178    /// Returns this [`CollationCaseFirst`] as a `&'static str`.
179    pub fn as_str(&self) -> &'static str {
180        match self {
181            CollationCaseFirst::Upper => "upper",
182            CollationCaseFirst::Lower => "lower",
183            CollationCaseFirst::Off => "off",
184        }
185    }
186}
187
188impl std::fmt::Display for CollationCaseFirst {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        std::fmt::Display::fmt(self.as_str(), f)
191    }
192}
193
194/// Setting that determines whether collation should consider whitespace and punctuation as base
195/// characters for purposes of comparison.
196#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
197#[serde(rename_all = "kebab-case")]
198#[non_exhaustive]
199pub enum CollationAlternate {
200    /// Whitespace and punctuation are considered base characters.
201    NonIgnorable,
202
203    /// Whitespace and punctuation are not considered base characters and are only distinguished at
204    /// strength levels greater than 3.
205    Shifted,
206}
207
208impl std::str::FromStr for CollationAlternate {
209    type Err = Error;
210
211    fn from_str(s: &str) -> Result<Self, Self::Err> {
212        match s {
213            "non-ignorable" => Ok(CollationAlternate::NonIgnorable),
214            "shifted" => Ok(CollationAlternate::Shifted),
215            _ => Err(ErrorKind::InvalidArgument {
216                message: format!("invalid collation alternate: {}", s),
217            }
218            .into()),
219        }
220    }
221}
222
223impl CollationAlternate {
224    /// Returns this [`CollationAlternate`] as a `&'static str`.
225    pub fn as_str(&self) -> &'static str {
226        match self {
227            CollationAlternate::NonIgnorable => "non-ignorable",
228            CollationAlternate::Shifted => "shifted",
229        }
230    }
231}
232
233impl std::fmt::Display for CollationAlternate {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        std::fmt::Display::fmt(self.as_str(), f)
236    }
237}
238
239/// Field that determines up to which characters are considered ignorable when alternate: "shifted".
240#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
241#[serde(rename_all = "kebab-case")]
242#[non_exhaustive]
243pub enum CollationMaxVariable {
244    /// Both whitespace and punctuation are "ignorable", i.e. not considered base characters.
245    Punct,
246
247    /// Whitespace are "ignorable", i.e. not considered base characters
248    Space,
249}
250
251impl std::str::FromStr for CollationMaxVariable {
252    type Err = Error;
253
254    fn from_str(s: &str) -> Result<Self, Self::Err> {
255        match s {
256            "punct" => Ok(CollationMaxVariable::Punct),
257            "space" => Ok(CollationMaxVariable::Space),
258            _ => Err(ErrorKind::InvalidArgument {
259                message: format!("invalid collation max variable: {}", s),
260            }
261            .into()),
262        }
263    }
264}
265
266impl CollationMaxVariable {
267    /// Returns this [`CollationMaxVariable`] as a `&'static str`.
268    pub fn as_str(&self) -> &'static str {
269        match self {
270            CollationMaxVariable::Punct => "punct",
271            CollationMaxVariable::Space => "space",
272        }
273    }
274}
275
276impl std::fmt::Display for CollationMaxVariable {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        std::fmt::Display::fmt(self.as_str(), f)
279    }
280}