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!();