//! Checked strings containing Unicode identifiers according to the //! [Unicode Standard Annex #31](https://www.unicode.org/reports/tr31/). //! //! # Examples //! //! ```rust //! use strck_ident::{IntoCk, unicode::UnicodeIdent}; //! //! assert!("foo".ck::().is_ok()); //! assert!("struct".ck::().is_ok()); //! assert!("Москва".ck::().is_ok()); //! assert!("東京".ck::().is_ok()); //! //! assert!("_identifier".ck::().is_err()); //! assert!("r#try".ck::().is_err()); //! assert!("👍".ck::().is_err()); //! ``` //! //! # Aliases //! //! This module exposes [`Ident`] and [`IdentBuf`], which alias `Ck` //! and `Check` respectively. These aliases are preferred to keep //! type signatures succinct. //! //! These are also exported under the root, and can be accessed as //! `strck_ident::Ident` and `strck_ident::IdentBuf`. use core::fmt; use strck::{Check, Ck, Invariant}; /// An [`Invariant`] for unicode identifiers according to /// [Unicode Standard Annex #31](https://www.unicode.org/reports/tr31/). /// /// # Invariants /// /// * The string is nonempty. /// * The first character is XID_Start. /// * Any following characters are XID_Continue. #[derive(Clone, Debug)] pub struct UnicodeIdent; /// Borrowed checked string containing a Unicode identifier. /// /// See [`UnicodeIdent`] for more details. pub type Ident = Ck; /// Owned checked string containing a Unicode identifier. /// /// See [`UnicodeIdent`] for more details. pub type IdentBuf = Check; /// The error type returned from checking invariants of [`UnicodeIdent`]. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Error { /// Empty string. Empty, /// The first character isn't XID_Start. Start(char), /// A trailing character isn't XID_Continue. Continue(char), } impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Empty => f.pad("empty"), Error::Start(ch) => write!(f, "invalid start '{ch}'"), Error::Continue(ch) => write!(f, "invalid continue '{ch}'"), } } } impl Invariant for UnicodeIdent { type Error = Error; fn check(slice: &str) -> Result<(), Self::Error> { let mut chars = slice.chars(); let start = chars.next().ok_or(Error::Empty)?; if !unicode_ident::is_xid_start(start) { return Err(Error::Start(start)); } for ch in chars { if !unicode_ident::is_xid_continue(ch) { return Err(Error::Continue(ch)); } } Ok(()) } } #[cfg(test)] mod tests { use super::{Error, UnicodeIdent}; use strck::IntoCk; #[test] fn test_invalid() { assert_eq!("".ck::().unwrap_err(), Error::Empty); assert_eq!("12345".ck::().unwrap_err(), Error::Start('1')); assert_eq!( "😂_foo".ck::().unwrap_err(), Error::Start('😂') ); assert_eq!( "foo_😂".ck::().unwrap_err(), Error::Continue('😂') ); assert_eq!( "hello.there".ck::().unwrap_err(), Error::Continue('.') ); assert_eq!( "\\as2mkf".ck::().unwrap_err(), Error::Start('\\') ); assert_eq!( "the book".ck::().unwrap_err(), Error::Continue(' ') ); assert_eq!(" book".ck::().unwrap_err(), Error::Start(' ')); assert_eq!("\n".ck::().unwrap_err(), Error::Start('\n')); assert_eq!( "_underscore".ck::().unwrap_err(), Error::Start('_') ); assert_eq!( "r#try".ck::().unwrap_err(), Error::Continue('#') ); } #[test] fn test_valid() { assert!("a2345".ck::().is_ok()); assert!("foo".ck::().is_ok()); assert!("snake_case".ck::().is_ok()); assert!("impl".ck::().is_ok()); assert!("岡林".ck::().is_ok()); } }