focaccia/lib.rs
1#![warn(clippy::all)]
2#![warn(clippy::pedantic)]
3#![warn(clippy::cargo)]
4#![cfg_attr(test, allow(clippy::non_ascii_literal))]
5#![cfg_attr(test, allow(clippy::shadow_unrelated))]
6#![allow(unknown_lints)]
7#![warn(missing_copy_implementations)]
8#![warn(missing_debug_implementations)]
9#![warn(missing_docs)]
10#![warn(rust_2018_idioms)]
11#![warn(trivial_casts, trivial_numeric_casts)]
12#![warn(unused_qualifications)]
13#![warn(variant_size_differences)]
14#![forbid(unsafe_code)]
15// Enable feature callouts in generated documentation:
16// https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html
17//
18// This approach is borrowed from tokio.
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, feature(doc_alias))]
21
22//! Unicode case folding methods for case-insensitive string comparisons.
23//!
24//! Focaccia supports full, ASCII, and Turkic [Unicode case folding] equality
25//! and [`Ordering`] comparisons.
26//!
27//! The primary entry point to Focaccia is the [`CaseFold`] enum. Focaccia also
28//! provides free functions for each case folding scheme.
29//!
30//! # Examples
31//!
32//! For Unicode text, Focaccia recommends the [`Full`](CaseFold::Full) folding
33//! scheme. To make a comparison:
34//!
35//! ```
36//! # use core::cmp::Ordering;
37//! # use focaccia::CaseFold;
38//! let fold = CaseFold::Full;
39//! assert_eq!(fold.casecmp("MASSE", "Maße"), Ordering::Equal);
40//! assert_eq!(fold.casecmp("São Paulo", "Sao Paulo"), Ordering::Greater);
41//!
42//! assert!(fold.case_eq("MASSE", "Maße"));
43//! assert!(!fold.case_eq("São Paulo", "Sao Paulo"));
44//! ```
45//!
46//! For text known to be ASCII, Focaccia can make a more performant comparison
47//! check:
48//!
49//! ```
50//! # use core::cmp::Ordering;
51//! # use focaccia::CaseFold;
52//! let fold = CaseFold::Ascii;
53//! assert_eq!(fold.casecmp("Crate: focaccia", "Crate: FOCACCIA"), Ordering::Equal);
54//! assert_eq!(fold.casecmp("Fabled", "failed"), Ordering::Less);
55//!
56//! assert!(fold.case_eq("Crate: focaccia", "Crate: FOCACCIA"));
57//! assert!(!fold.case_eq("Fabled", "failed"));
58//! ```
59//!
60//! ASCII case comparison can be checked on a byte slice:
61//!
62//! ```
63//! # use core::cmp::Ordering;
64//! # use focaccia::{ascii_casecmp, ascii_case_eq};
65//! assert_eq!(ascii_casecmp(b"Artichoke Ruby", b"artichoke ruby"), Ordering::Equal);
66//! assert!(ascii_case_eq(b"Artichoke Ruby", b"artichoke ruby"));
67//! ```
68//!
69//! Turkic case folding is similar to full case folding with additional mappings
70//! for [dotted and dotless I]:
71//!
72//! ```
73//! # use core::cmp::Ordering;
74//! # use focaccia::CaseFold;
75//! let fold = CaseFold::Turkic;
76//! assert_eq!(fold.casecmp("İstanbul", "istanbul"), Ordering::Equal);
77//! assert_ne!(fold.casecmp("İstanbul", "Istanbul"), Ordering::Equal);
78//!
79//! assert!(fold.case_eq("İstanbul", "istanbul"));
80//! assert!(!fold.case_eq("İstanbul", "Istanbul"));
81//! ```
82//!
83//! # `no_std`
84//!
85//! Focaccia is `no_std` compatible and only depends on [`core`]. Focaccia does not
86//! link to `alloc` in its `no_std` configuration.
87//!
88//! # Unicode Version
89//!
90//! Focaccia implements Unicode case folding with the Unicode 16.0.0 case folding
91//! ruleset.
92//!
93//! Each new release of Unicode may bring updates to the `CaseFolding.txt` which is
94//! the source for the folding mappings in this crate. Updates to the case folding
95//! rules will be accompanied with a minor version bump.
96//!
97//! [Unicode case folding]: https://www.w3.org/International/wiki/Case_folding
98//! [`Ordering`]: core::cmp::Ordering
99//! [dotted and dotless I]: https://en.wikipedia.org/wiki/Dotted_and_dotless_I
100
101#![no_std]
102#![doc(html_root_url = "https://docs.rs/focaccia/2.0.0")]
103
104#[cfg(any(test, doctest))]
105extern crate std;
106
107use core::cmp::Ordering;
108use core::convert::TryFrom;
109use core::fmt;
110
111#[cfg(test)]
112mod exhaustive;
113mod folding;
114
115/// Focaccia is derived from Unicode Data Files and is subject to Unicode License
116/// v3.
117///
118/// See <https://www.unicode.org/terms_of_use.html>.
119///
120/// # Unicode License v3
121///
122/// ```txt
123#[doc = include_str!("../LICENSE-UNICODE")]
124/// ```
125#[cfg(doc)]
126#[cfg_attr(docsrs, doc(cfg(doc)))]
127pub mod unicode_terms {}
128
129pub use folding::{
130 ascii_case_eq, ascii_casecmp, unicode_full_case_eq, unicode_full_casecmp,
131 unicode_full_turkic_case_eq, unicode_full_turkic_casecmp,
132};
133
134/// Unicode case folding strategies.
135///
136/// Unicode case folding data supports both implementations that require simple
137/// case foldings (where string lengths don't change), and implementations that
138/// allow full case folding (where string lengths may grow). Note that where
139/// they can be supported, the full case foldings are superior: for example,
140/// they allow "MASSE" and "Maße" to match.
141///
142/// The `CaseFold` enum supports the [folding strategies available in Ruby].
143///
144/// # Examples
145///
146/// For Unicode text, the default folding scheme is [`Full`](CaseFold::Full). To
147/// make a comparison:
148///
149/// ```
150/// # use core::cmp::Ordering;
151/// # use focaccia::CaseFold;
152/// let fold = CaseFold::Full;
153/// assert_eq!(fold.casecmp("MASSE", "Maße"), Ordering::Equal);
154/// assert_eq!(fold.casecmp("São Paulo", "Sao Paulo"), Ordering::Greater);
155///
156/// assert!(fold.case_eq("MASSE", "Maße"));
157/// assert!(!fold.case_eq("São Paulo", "Sao Paulo"));
158/// ```
159///
160/// For text known to be ASCII, Focaccia can make a more performant comparison
161/// check:
162///
163/// ```
164/// # use core::cmp::Ordering;
165/// # use focaccia::CaseFold;
166/// let fold = CaseFold::Ascii;
167/// assert_eq!(fold.casecmp("Crate: focaccia", "Crate: FOCACCIA"), Ordering::Equal);
168/// assert_eq!(fold.casecmp("Fabled", "failed"), Ordering::Less);
169///
170/// assert!(fold.case_eq("Crate: focaccia", "Crate: FOCACCIA"));
171/// assert!(!fold.case_eq("Fabled", "failed"));
172/// ```
173///
174/// Turkic case folding is similar to full case folding with additional mappings
175/// for [dotted and dotless I]:
176///
177/// ```
178/// # use core::cmp::Ordering;
179/// # use focaccia::CaseFold;
180/// let fold = CaseFold::Turkic;
181/// assert_eq!(fold.casecmp("İstanbul", "istanbul"), Ordering::Equal);
182/// assert_ne!(fold.casecmp("İstanbul", "Istanbul"), Ordering::Equal);
183///
184/// assert!(fold.case_eq("İstanbul", "istanbul"));
185/// assert!(!fold.case_eq("İstanbul", "Istanbul"));
186/// ```
187///
188/// [folding strategies available in Ruby]: https://ruby-doc.org/core-3.1.2/String.html#method-i-downcase
189/// [dotted and dotless I]: https://en.wikipedia.org/wiki/Dotted_and_dotless_I
190#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
191pub enum CaseFold {
192 /// Full Unicode case mapping, suitable for most languages (see [`Turkic`]
193 /// and [`Lithuanian`] strategies for exceptions). Context-dependent case
194 /// mapping as described in Table 3-14 of the Unicode standard is currently
195 /// not supported.
196 ///
197 /// [`Turkic`]: Self::Turkic
198 /// [`Lithuanian`]: Self::Lithuanian
199 Full,
200 /// Only the ASCII region, i.e. the characters "A" to "Z" and "a" to "z",
201 /// are affected.
202 Ascii,
203 /// Full Unicode case mapping, adapted for Turkic languages (Turkish,
204 /// Azerbaijani, ...). This means that upper case I is mapped to lower case
205 /// dotless i, and so on.
206 Turkic,
207 /// Currently, just full Unicode case mapping. In the future, full Unicode
208 /// case mapping adapted for Lithuanian (keeping the dot on the lower case i
209 /// even if there is an accent on top).
210 Lithuanian,
211}
212
213impl Default for CaseFold {
214 /// Default to full Unicode case folding.
215 ///
216 /// See [`CaseFold::Full`].
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// # use focaccia::CaseFold;
222 /// assert_eq!(CaseFold::default(), CaseFold::Full);
223 ///
224 /// assert!(CaseFold::default().case_eq("MASSE", "Maße"));
225 /// assert!(!CaseFold::default().case_eq("São Paulo", "Sao Paulo"));
226 /// ```
227 #[inline]
228 fn default() -> Self {
229 Self::Full
230 }
231}
232
233impl CaseFold {
234 /// Construct a new full Unicode case folding.
235 ///
236 /// See [`CaseFold::Full`].
237 ///
238 /// # Examples
239 ///
240 /// ```
241 /// # use focaccia::CaseFold;
242 /// const FOLD: CaseFold = CaseFold::new();
243 ///
244 /// assert_eq!(CaseFold::new(), CaseFold::Full);
245 ///
246 /// assert!(CaseFold::new().case_eq("MASSE", "Maße"));
247 /// assert!(!CaseFold::new().case_eq("São Paulo", "Sao Paulo"));
248 /// ```
249 #[inline]
250 #[must_use]
251 pub const fn new() -> Self {
252 Self::Full
253 }
254
255 /// Make a case-insensitive string comparison based on the dispatching
256 /// folding strategy.
257 ///
258 /// Return `Ordering::Equal` if the given strings match when folding case.
259 /// For example, when using `CaseFold::Full`, `ß` is considered equal to
260 /// `ss`. The differences between the folding strategies are documented on
261 /// the variants of the [`CaseFold`] enum.
262 ///
263 /// This function is a wrapper around the underlying scheme-specific
264 /// functions.
265 ///
266 /// # Examples – Full case folding
267 ///
268 /// ```
269 /// # use core::cmp::Ordering;
270 /// # use focaccia::CaseFold;
271 /// let fold = CaseFold::Full;
272 /// assert_eq!(fold.casecmp("MASSE", "Maße"), Ordering::Equal);
273 /// assert_eq!(fold.casecmp("São Paulo", "Sao Paulo"), Ordering::Greater);
274 /// ```
275 ///
276 /// # Examples – ASCII case folding
277 ///
278 /// ```
279 /// # use core::cmp::Ordering;
280 /// # use focaccia::CaseFold;
281 /// let fold = CaseFold::Ascii;
282 /// assert_eq!(fold.casecmp("Crate: focaccia", "Crate: FOCACCIA"), Ordering::Equal);
283 /// assert_eq!(fold.casecmp("Fabled", "failed"), Ordering::Less);
284 /// ```
285 ///
286 /// # Examples – Turkic case folding
287 ///
288 /// ```
289 /// # use core::cmp::Ordering;
290 /// # use focaccia::CaseFold;
291 /// let fold = CaseFold::Turkic;
292 /// assert_eq!(fold.casecmp("İstanbul", "istanbul"), Ordering::Equal);
293 /// assert_ne!(fold.casecmp("İstanbul", "Istanbul"), Ordering::Equal);
294 /// ```
295 #[inline]
296 #[must_use]
297 pub fn casecmp(self, left: &str, right: &str) -> Ordering {
298 match self {
299 Self::Full | Self::Lithuanian => unicode_full_casecmp(left, right),
300 Self::Ascii => ascii_casecmp(left.as_bytes(), right.as_bytes()),
301 Self::Turkic => unicode_full_turkic_casecmp(left, right),
302 }
303 }
304
305 /// Make a case-insensitive string equality check based on the dispatching
306 /// folding strategy.
307 ///
308 /// Return `true` if the given strings match when folding case. For example,
309 /// when using `CaseFold::Full`, `ß` is considered equal to `ss`. The
310 /// differences between the folding strategies are documented on the
311 /// variants of the [`CaseFold`] enum.
312 ///
313 /// This function is a wrapper around the underlying scheme-specific
314 /// functions.
315 ///
316 /// # Examples – Full case folding
317 ///
318 /// ```
319 /// # use focaccia::CaseFold;
320 /// let fold = CaseFold::Full;
321 /// assert!(fold.case_eq("MASSE", "Maße"));
322 /// assert!(!fold.case_eq("São Paulo", "Sao Paulo"));
323 /// ```
324 ///
325 /// # Examples – ASCII case folding
326 ///
327 /// ```
328 /// # use focaccia::CaseFold;
329 /// let fold = CaseFold::Ascii;
330 /// assert!(fold.case_eq("Crate: focaccia", "Crate: FOCACCIA"));
331 /// assert!(!fold.case_eq("Fabled", "failed"));
332 /// ```
333 ///
334 /// # Examples – Turkic case folding
335 ///
336 /// ```
337 /// # use focaccia::CaseFold;
338 /// let fold = CaseFold::Turkic;
339 /// assert!(fold.case_eq("İstanbul", "istanbul"));
340 /// assert!(!fold.case_eq("İstanbul", "Istanbul"));
341 /// ```
342 #[inline]
343 #[must_use]
344 pub fn case_eq(self, left: &str, right: &str) -> bool {
345 match self {
346 Self::Full | Self::Lithuanian => unicode_full_case_eq(left, right),
347 Self::Ascii => ascii_case_eq(left.as_bytes(), right.as_bytes()),
348 Self::Turkic => unicode_full_turkic_case_eq(left, right),
349 }
350 }
351}
352
353/// Error type for returned when a folding scheme could not be resolved in a
354/// [`TryFrom`] implementation.
355///
356/// `NoSuchCaseFoldingScheme` implements [`core::error::Error`].
357///
358/// # Examples
359///
360/// ```
361/// # use core::convert::TryFrom;
362/// # use focaccia::{CaseFold, NoSuchCaseFoldingScheme};
363/// assert_eq!(CaseFold::try_from(None::<&str>), Ok(CaseFold::Full));
364/// assert_eq!(CaseFold::try_from(Some("ascii")), Ok(CaseFold::Ascii));
365/// assert_eq!(CaseFold::try_from(Some("turkic")), Ok(CaseFold::Turkic));
366/// assert_eq!(CaseFold::try_from(Some("lithuanian")), Ok(CaseFold::Lithuanian));
367/// assert_eq!(CaseFold::try_from(Some("xxx")), Err(NoSuchCaseFoldingScheme::new()));
368/// ```
369#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
370pub struct NoSuchCaseFoldingScheme {
371 _private: (),
372}
373
374impl NoSuchCaseFoldingScheme {
375 /// Construct a new [`NoSuchCaseFoldingScheme`] error.
376 ///
377 /// # Examples
378 ///
379 /// ```
380 /// # use focaccia::NoSuchCaseFoldingScheme;
381 /// const ERR: NoSuchCaseFoldingScheme = NoSuchCaseFoldingScheme::new();
382 /// ```
383 #[inline]
384 #[must_use]
385 pub const fn new() -> Self {
386 Self { _private: () }
387 }
388}
389
390impl fmt::Display for NoSuchCaseFoldingScheme {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 f.write_str("No such Unicode case folding scheme exists")
393 }
394}
395
396impl core::error::Error for NoSuchCaseFoldingScheme {}
397
398impl TryFrom<Option<&str>> for CaseFold {
399 type Error = NoSuchCaseFoldingScheme;
400
401 #[inline]
402 fn try_from(value: Option<&str>) -> Result<Self, Self::Error> {
403 Self::try_from(value.map(str::as_bytes))
404 }
405}
406
407impl TryFrom<Option<&[u8]>> for CaseFold {
408 type Error = NoSuchCaseFoldingScheme;
409
410 #[inline]
411 fn try_from(value: Option<&[u8]>) -> Result<Self, Self::Error> {
412 match value {
413 None => Ok(Self::Full),
414 Some(scheme) if scheme == b"ascii" => Ok(Self::Ascii),
415 Some(scheme) if scheme == b"turkic" => Ok(Self::Turkic),
416 Some(scheme) if scheme == b"lithuanian" => Ok(Self::Lithuanian),
417 Some(_) => Err(NoSuchCaseFoldingScheme::new()),
418 }
419 }
420}
421
422// Tests using IDN case folding test vectors:
423#[cfg(test)]
424mod tests {
425 use core::cmp::Ordering;
426
427 use crate::{CaseFold, NoSuchCaseFoldingScheme};
428
429 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.2
430 #[test]
431 fn case_folding_ascii_case_eq() {
432 let input = "CAFE";
433 let output = "cafe";
434
435 assert!(CaseFold::Full.case_eq(input, output));
436 assert!(CaseFold::Full.case_eq(output, input));
437
438 assert!(CaseFold::Ascii.case_eq(input, output));
439 assert!(CaseFold::Ascii.case_eq(output, input));
440
441 assert!(CaseFold::Turkic.case_eq(input, output));
442 assert!(CaseFold::Turkic.case_eq(output, input));
443
444 assert!(CaseFold::Lithuanian.case_eq(input, output));
445 assert!(CaseFold::Lithuanian.case_eq(output, input));
446 }
447
448 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.2
449 #[test]
450 fn case_folding_ascii_casecmp() {
451 let input = "CAFE";
452 let output = "cafe";
453
454 assert_eq!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
455 assert_eq!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
456
457 assert_eq!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
458 assert_eq!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
459
460 assert_eq!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
461 assert_eq!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
462
463 assert_eq!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
464 assert_eq!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
465 }
466
467 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.3
468 #[test]
469 fn case_folding_8bit_case_eq() {
470 let input = "ß";
471 let output = "ss";
472
473 assert!(CaseFold::Full.case_eq(input, output));
474 assert!(CaseFold::Full.case_eq(output, input));
475
476 assert!(!CaseFold::Ascii.case_eq(input, output));
477 assert!(!CaseFold::Ascii.case_eq(output, input));
478
479 assert!(CaseFold::Turkic.case_eq(input, output));
480 assert!(CaseFold::Turkic.case_eq(output, input));
481
482 assert!(CaseFold::Lithuanian.case_eq(input, output));
483 assert!(CaseFold::Lithuanian.case_eq(output, input));
484 }
485
486 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.3
487 #[test]
488 fn case_folding_8bit_casecmp() {
489 let input = "ß";
490 let output = "ss";
491
492 assert_eq!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
493 assert_eq!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
494
495 assert_ne!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
496 assert_ne!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
497
498 assert_eq!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
499 assert_eq!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
500
501 assert_eq!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
502 assert_eq!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
503 }
504
505 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.4
506 #[test]
507 fn case_folding_turkic_capital_i_with_dot() {
508 let input = "İ";
509 let output = "i";
510
511 assert!(CaseFold::Turkic.case_eq(input, output));
512 assert!(CaseFold::Turkic.case_eq(output, input));
513
514 assert_eq!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
515 assert_eq!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
516 }
517
518 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.5
519 //
520 // Multibyte folding is not supported.
521 #[test]
522 fn case_folding_multibyte_case_eq() {
523 let input = "Ńͺ";
524 let output = "ń ι";
525
526 assert!(!CaseFold::Full.case_eq(input, output));
527 assert!(!CaseFold::Full.case_eq(output, input));
528
529 assert!(!CaseFold::Ascii.case_eq(input, output));
530 assert!(!CaseFold::Ascii.case_eq(output, input));
531
532 assert!(!CaseFold::Turkic.case_eq(input, output));
533 assert!(!CaseFold::Turkic.case_eq(output, input));
534
535 assert!(!CaseFold::Lithuanian.case_eq(input, output));
536 assert!(!CaseFold::Lithuanian.case_eq(output, input));
537 }
538
539 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.5
540 //
541 // Multibyte folding is not supported.
542 #[test]
543 fn case_folding_multibyte_casecmp() {
544 let input = "Ńͺ";
545 let output = "ń ι";
546
547 assert_ne!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
548 assert_ne!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
549
550 assert_ne!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
551 assert_ne!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
552
553 assert_ne!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
554 assert_ne!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
555
556 assert_ne!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
557 assert_ne!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
558 }
559
560 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.6
561 //
562 // These folding operations are not supported.
563 #[test]
564 fn case_folding_4_6_case_eq() {
565 let input = "℡㏆𝞻";
566 let output = "telc∕kgσ";
567
568 assert!(!CaseFold::Full.case_eq(input, output));
569 assert!(!CaseFold::Full.case_eq(output, input));
570
571 assert!(!CaseFold::Ascii.case_eq(input, output));
572 assert!(!CaseFold::Ascii.case_eq(output, input));
573
574 assert!(!CaseFold::Turkic.case_eq(input, output));
575 assert!(!CaseFold::Turkic.case_eq(output, input));
576
577 assert!(!CaseFold::Lithuanian.case_eq(input, output));
578 assert!(!CaseFold::Lithuanian.case_eq(output, input));
579 }
580
581 // https://tools.ietf.org/html/draft-josefsson-idn-test-vectors-00#section-4.6
582 //
583 // These folding operations are not supported.
584 #[test]
585 fn case_folding_4_6_casecmp() {
586 let input = "℡㏆𝞻";
587 let output = "telc∕kgσ";
588
589 assert_ne!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
590 assert_ne!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
591
592 assert_ne!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
593 assert_ne!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
594
595 assert_ne!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
596 assert_ne!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
597
598 assert_ne!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
599 assert_ne!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
600 }
601
602 #[test]
603 fn error_display_is_not_empty() {
604 use core::fmt::Write as _;
605 use std::string::String;
606
607 let tc = NoSuchCaseFoldingScheme::new();
608 let mut buf = String::new();
609 write!(&mut buf, "{tc}").unwrap();
610 assert!(!buf.is_empty());
611 }
612}
613
614// Ensure code blocks in `README.md` compile.
615//
616// This module and macro declaration should be kept at the end of the file, in
617// order to not interfere with code coverage.
618#[cfg(doctest)]
619macro_rules! readme {
620 ($x:expr) => {
621 #[doc = $x]
622 mod readme {}
623 };
624 () => {
625 readme!(include_str!("../README.md"));
626 };
627}
628#[cfg(doctest)]
629readme!();