1include!(concat!(env!("OUT_DIR"), "/width_mapping.rs"));
2
3use crate::bidi;
4use crate::common;
5use lazy_static::lazy_static;
6use precis_core::profile::{PrecisFastInvocation, Profile, Rules};
7use precis_core::Codepoints;
8use precis_core::{Error, UnexpectedError};
9use precis_core::{IdentifierClass, StringClass};
10use std::borrow::Cow;
11
12fn get_decomposition_mapping(cp: u32) -> Option<u32> {
13 WIDE_NARROW_MAPPING
14 .binary_search_by(|cps| cps.0.partial_cmp(&cp).unwrap())
15 .map(|x| WIDE_NARROW_MAPPING[x].1)
16 .ok()
17}
18
19fn has_width_mapping(c: char) -> bool {
20 get_decomposition_mapping(c as u32).is_some()
21}
22
23fn width_mapping_rule<'a, T>(s: T) -> Result<Cow<'a, str>, Error>
24where
25 T: Into<Cow<'a, str>>,
26{
27 let s = s.into();
28 match s.find(has_width_mapping) {
29 None => Ok(s),
30 Some(pos) => {
31 let mut res = String::from(&s[..pos]);
32 res.reserve(s.len() - res.len());
33 for c in s[pos..].chars() {
34 res.push(match get_decomposition_mapping(c as u32) {
35 Some(d) => {
36 char::from_u32(d).ok_or(Error::Unexpected(UnexpectedError::Undefined))?
37 }
38 None => c,
39 });
40 }
41 Ok(res.into())
42 }
43 }
44}
45
46fn directionality_rule<'a, T>(s: T) -> Result<Cow<'a, str>, Error>
47where
48 T: Into<Cow<'a, str>>,
49{
50 let s = s.into();
51 if bidi::has_rtl(&s) {
52 bidi::satisfy_bidi_rule(&s)
53 .then_some(s)
54 .ok_or(Error::Invalid)
55 } else {
56 Ok(s)
57 }
58}
59
60#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
88pub struct UsernameCaseMapped(IdentifierClass);
89
90impl UsernameCaseMapped {
91 pub fn new() -> Self {
93 Self(IdentifierClass::default())
94 }
95}
96
97impl Profile for UsernameCaseMapped {
98 fn prepare<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
99 where
100 S: Into<Cow<'a, str>>,
101 {
102 let s = self.width_mapping_rule(s)?;
103 let s = (!s.is_empty()).then_some(s).ok_or(Error::Invalid)?;
104 self.0.allows(&s)?;
105 Ok(s)
106 }
107
108 fn enforce<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
109 where
110 S: Into<Cow<'a, str>>,
111 {
112 let s = self.prepare(s)?;
113 let s = self.case_mapping_rule(s)?;
114 let s = self.normalization_rule(s)?;
115 let s = (!s.is_empty()).then_some(s).ok_or(Error::Invalid)?;
116 self.directionality_rule(s)
117 }
118
119 fn compare<A, B>(&self, s1: A, s2: B) -> Result<bool, Error>
120 where
121 A: AsRef<str>,
122 B: AsRef<str>,
123 {
124 Ok(self.enforce(s1.as_ref())? == self.enforce(s2.as_ref())?)
125 }
126}
127
128impl Rules for UsernameCaseMapped {
129 fn width_mapping_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
130 where
131 T: Into<Cow<'a, str>>,
132 {
133 width_mapping_rule(s)
134 }
135
136 fn case_mapping_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
137 where
138 T: Into<Cow<'a, str>>,
139 {
140 common::case_mapping_rule(s)
141 }
142
143 fn normalization_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
144 where
145 T: Into<Cow<'a, str>>,
146 {
147 common::normalization_form_nfc(s)
148 }
149
150 fn directionality_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
151 where
152 T: Into<Cow<'a, str>>,
153 {
154 directionality_rule(s)
155 }
156}
157
158fn get_username_case_mapped_profile() -> &'static UsernameCaseMapped {
159 lazy_static! {
160 static ref USERNAME_CASE_MAPPED: UsernameCaseMapped = UsernameCaseMapped::default();
161 }
162 &USERNAME_CASE_MAPPED
163}
164
165impl PrecisFastInvocation for UsernameCaseMapped {
166 fn prepare<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
167 where
168 S: Into<Cow<'a, str>>,
169 {
170 get_username_case_mapped_profile().prepare(s)
171 }
172
173 fn enforce<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
174 where
175 S: Into<Cow<'a, str>>,
176 {
177 get_username_case_mapped_profile().enforce(s)
178 }
179
180 fn compare<A, B>(s1: A, s2: B) -> Result<bool, Error>
181 where
182 A: AsRef<str>,
183 B: AsRef<str>,
184 {
185 get_username_case_mapped_profile().compare(s1, s2)
186 }
187}
188
189#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
217pub struct UsernameCasePreserved(IdentifierClass);
218
219impl UsernameCasePreserved {
220 pub fn new() -> Self {
222 Self(IdentifierClass::default())
223 }
224}
225
226impl Profile for UsernameCasePreserved {
227 fn prepare<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
228 where
229 S: Into<Cow<'a, str>>,
230 {
231 let s = self.width_mapping_rule(s)?;
232 let s = (!s.is_empty()).then_some(s).ok_or(Error::Invalid)?;
233 self.0.allows(&s)?;
234 Ok(s)
235 }
236
237 fn enforce<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
238 where
239 S: Into<Cow<'a, str>>,
240 {
241 let s = self.prepare(s)?;
242 let s = self.normalization_rule(s)?;
243 let s = (!s.is_empty()).then_some(s).ok_or(Error::Invalid)?;
244 self.directionality_rule(s)
245 }
246
247 fn compare<A, B>(&self, s1: A, s2: B) -> Result<bool, Error>
248 where
249 A: AsRef<str>,
250 B: AsRef<str>,
251 {
252 Ok(self.enforce(s1.as_ref())? == self.enforce(s2.as_ref())?)
253 }
254}
255
256impl Rules for UsernameCasePreserved {
257 fn width_mapping_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
258 where
259 T: Into<Cow<'a, str>>,
260 {
261 width_mapping_rule(s)
262 }
263
264 fn normalization_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
265 where
266 T: Into<Cow<'a, str>>,
267 {
268 common::normalization_form_nfc(s)
269 }
270
271 fn directionality_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
272 where
273 T: Into<Cow<'a, str>>,
274 {
275 directionality_rule(s)
276 }
277}
278
279fn get_username_case_preserved_profile() -> &'static UsernameCasePreserved {
280 lazy_static! {
281 static ref USERNAME_CASE_PRESERVED: UsernameCasePreserved =
282 UsernameCasePreserved::default();
283 }
284 &USERNAME_CASE_PRESERVED
285}
286
287impl PrecisFastInvocation for UsernameCasePreserved {
288 fn prepare<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
289 where
290 S: Into<Cow<'a, str>>,
291 {
292 get_username_case_preserved_profile().prepare(s)
293 }
294
295 fn enforce<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
296 where
297 S: Into<Cow<'a, str>>,
298 {
299 get_username_case_preserved_profile().enforce(s)
300 }
301
302 fn compare<A, B>(s1: A, s2: B) -> Result<bool, Error>
303 where
304 A: AsRef<str>,
305 B: AsRef<str>,
306 {
307 get_username_case_preserved_profile().compare(s1, s2)
308 }
309}
310
311#[cfg(test)]
312mod profile_rules {
313 use crate::usernames::*;
314
315 #[test]
316 fn test_width_mapping_rule() {
317 let res = width_mapping_rule("");
318 assert_eq!(res, Ok(Cow::from("")));
319
320 let res = width_mapping_rule("TestName");
322 assert_eq!(res, Ok(Cow::from("TestName")));
323
324 let res = width_mapping_rule("\u{ff03}");
326 assert_eq!(res, Ok(Cow::from("\u{0023}")));
327
328 let res = width_mapping_rule("a\u{ff03}");
329 assert_eq!(res, Ok(Cow::from("a\u{0023}")));
330
331 let res = width_mapping_rule("\u{ff03}a");
332 assert_eq!(res, Ok(Cow::from("\u{0023}a")));
333
334 let res = width_mapping_rule("\u{ff03}\u{ff03}\u{ff03}");
335 assert_eq!(res, Ok(Cow::from("\u{0023}\u{0023}\u{0023}")));
336 }
337
338 #[test]
339 fn test_directionality_rule() {
340 let res = directionality_rule("");
341 assert_eq!(res, Ok(Cow::from("")));
342
343 let res = directionality_rule("Hello");
345 assert_eq!(res, Ok(Cow::from("Hello")));
346
347 let res = directionality_rule("\u{05be}");
349 assert_eq!(res, Ok(Cow::from("\u{05be}")));
350
351 let res = directionality_rule("\u{00aa}");
353 assert_eq!(res, Ok(Cow::from("\u{00aa}")));
354
355 let res = directionality_rule("\u{05be}Hello");
357 assert_eq!(res, Err(Error::Invalid));
358 }
359
360 #[test]
361 fn username_name_case_mapped_profile() {
362 let profile = UsernameCaseMapped::new();
363
364 let res = profile.prepare("XxXxX");
365 assert_eq!(res, Ok(Cow::from("XxXxX")));
366
367 let res = profile.enforce("XxXxX");
368 assert_eq!(res, Ok(Cow::from("xxxxx")));
369
370 let res = profile.compare("heLLo", "Hello");
371 assert_eq!(res, Ok(true));
372 }
373
374 #[test]
375 fn username_name_case_preserved_profile() {
376 let profile = UsernameCasePreserved::new();
377
378 let res = profile.prepare("XxXxX");
379 assert_eq!(res, Ok(Cow::from("XxXxX")));
380
381 let res = profile.enforce("XxXxX");
382 assert_eq!(res, Ok(Cow::from("XxXxX")));
383
384 let res = profile.compare("Hello", "Hello");
385 assert_eq!(res, Ok(true));
386 }
387}