//! [![github-img]][github-url] [![crates-img]][crates-url] [![docs-img]][docs-url] //! //! [github-url]: https://github.com/QnnOkabayashi/strck //! [crates-url]: https://crates.io/crates/strck //! [docs-url]: crate //! [github-img]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github //! [crates-img]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust //! [docs-img]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K //! //! Checked owned and borrowed strings. //! //! # Overview //! //! The Rust standard library provides the `String` and `str` types, which wrap //! `Vec` and `[u8]` respectively, with the invariant that the contents //! are valid UTF-8. //! //! This crate abstracts the idea of type-level invariants on strings by //! introducing the immutable [`Check`] and [`Ck`] types, where the invariants //! are determined by a generic [`Invariant`] type parameter. Implementing //! the [`Invariant`] trait is left to other crates, such as [`strck_ident`]. //! //! "strck" comes from "str check", similar to how rustc has typeck and //! borrowck for type check and borrow check respectively. //! //! # Motivation //! //! Libraries working with string-like types with certain properties, like identifiers, //! quickly become confusing as `&str` and `String` begin to pollute type signatures //! everywhere. One solution is to manually implement an owned checked string type //! like [`syn::Ident`] to disambiguate the type signatures and validate the string. //! The downside is that new values cannot be created without allocation, //! which is unnecessary when only a borrowed version is required. //! //! `strck` solves this issue by providing a checked borrowed string type, [`Ck`], //! alongside a checked owned string type, [`Check`]. These serve as thin wrappers //! around `str` and `String`[^1] respectively, and prove at the type level that //! the contents satisfy the [`Invariant`] that the wrapper is generic over. //! //! [^1]: [`Check`] can actually be backed by any `'static + AsRef` type, //! but `String` is the default. //! //! # Use cases //! //! ### Checked strings without allocating //! //! The main benefit `strck` offers is validating borrowed strings via the //! [`Ck`] type without having to allocate in the result. //! //! ```rust //! use strck_ident::{Ck, IntoCk, rust::RustIdent}; //! //! let this_ident: &Ck = "this".ck().unwrap(); //! ``` //! //! ### Checked zero-copy deserialization //! //! When the `serde` feature flag is enabled, [`Ck`]s can be used to perform //! checked zero-copy deserialization, which requires the //! [`#[serde(borrow)]`][borrow] attribute. //! //! ```rust //! # use serde::{Serialize, Deserialize}; //! use strck_ident::{Ck, unicode::UnicodeIdent}; //! //! #[derive(Serialize, Deserialize)] //! struct Player<'a> { //! #[serde(borrow)] //! username: &'a Ck, //! level: u32, //! } //! ``` //! //! Note that this code sample explicitly uses `Ck` to demonstrate //! that the type is a [`Ck`]. However, [`strck_ident`] provides [`Ident`] as an //! alias for `Ck`, which should be used in practice. //! //! ### Infallible parsing //! //! For types where string validation is relatively cheap but parsing is costly //! and fallible, `strck` can be used with a custom [`Invariant`] as an input to //! make an infallible parsing function. //! //! # Postfix construction with `IntoCk` and `IntoCheck` //! //! This crate exposes two helper traits, [`IntoCk`] and [`IntoCheck`]. When in //! scope, the [`.ck()`] and [`.check()`] functions can be used to create //! [`Ck`]s and [`Check`]s respectively: //! //! ```rust //! use strck_ident::{IntoCheck, IntoCk, unicode::UnicodeIdent}; //! //! let this_ident = "this".ck::().unwrap(); //! let this_foo_ident = format!("{}_foo", this_ident).check::().unwrap(); //! ``` //! //! # Feature flags //! //! * `serde`: Implements `Serialize`/`Deserialize` for [`Check`]s and [`Ck`]s, //! where the invariants are checked during deserialization. Disabled by default. //! //! [`syn::Ident`]: https://docs.rs/syn/latest/syn/struct.Ident.html //! [`strck_ident`]: https://docs.rs/strck_ident //! [`Ident`]: https://docs.rs/strck_ident/latest/strck_ident/unicode/type.Ident.html //! [borrow]: https://serde.rs/lifetimes.html#borrowing-data-in-a-derived-impl //! [`.ck()`]: IntoCk::ck //! [`.check()`]: IntoCheck::check use core::{borrow, cmp, fmt, hash, marker, ops, str}; mod partial_eq; #[cfg(feature = "serde")] mod serde; /// Owned immutable string with invariants. /// /// Similar to how `String` derefs to `&str`, [`Check`] derefs to [`&Ck`](Ck). /// This means APIs requiring `&Check` as an argument should instead consider /// accepting `&Ck` for more flexibility. /// /// # Buffers /// /// By default, this type is backed by a `String`, but it can also be backed by /// any `AsRef + 'static` type. In particular, types like [`SmolStr`] are /// good candidates since they're designed to be immutable. /// /// It's recommended to use a type alias when using a custom backing type, since /// extra generics can make the type signature long. /// /// [`SmolStr`]: https://docs.rs/smol_str/latest/smol_str/struct.SmolStr.html #[derive(Clone)] #[repr(transparent)] pub struct Check + 'static = String> { _marker: marker::PhantomData, buf: B, } /// Borrowed immutable string with invariants. /// /// [`Ck`] is a DST, and therefore must always live behind a pointer. This means /// you'll usually see it as `&Ck` in type signatures. /// /// # Deserialization /// /// See the [crate-level documentation] for details on how to use [`Ck`] for /// checked zero-copy deserialization. /// /// [crate-level documentation]: crate#checked-zero-copy-deserialization #[repr(transparent)] pub struct Ck { _marker: marker::PhantomData, slice: str, } /// Invariant for a [`Ck`] or [`Check`]. /// /// The [`Ck`] and [`Check`] types are checked strings types that make guarantees /// about the contents of the string. These guarantees are determined by this /// trait, `Invariant` which distinguishes whether or not a string upholds some /// arbitrary invariants via the [`Invariant::check`] function. If the `Err` is /// returned, then the invariant is broken, and the `Ck` or `Check` generic over /// the invariant cannot be constructed. /// /// # Examples /// /// Declaring an invariant that the string contains no whitespace: /// ```rust /// # use strck::Invariant; /// struct NoWhitespace; /// /// impl Invariant for NoWhitespace { /// type Error = char; /// /// fn check(slice: &str) -> Result<(), Self::Error> { /// match slice.chars().find(|ch| ch.is_whitespace()) { /// Some(ch) => Err(ch), /// None => Ok(()), /// } /// } /// } /// ``` pub trait Invariant: Sized { /// The type returned in the event that an invariant is broken. /// /// When formatting, `Error` should not be capitalized and should not end /// with a period. type Error: fmt::Display; /// Returns `Ok` if the string upholds the invariant, otherwise `Err`. /// /// This function is used internally in [`Check::from_buf`] and [`Ck::from_slice`]. fn check(slice: &str) -> Result<(), Self::Error>; } /// Conversion into a [`Ck`]. pub trait IntoCk: Sized + AsRef { /// Returns a validated [`Ck`] borrowing from `self`. /// /// # Examples /// /// Creating an Rust ident containing `this`: /// ```rust /// use strck_ident::{IntoCk, rust::Ident}; /// /// let this_ident: &Ident = "this".ck().unwrap(); /// ``` fn ck(&self) -> Result<&Ck, I::Error>; } impl> IntoCk for T { fn ck(&self) -> Result<&Ck, I::Error> { Ck::from_slice(self.as_ref()) } } /// Conversion into a [`Check`]. pub trait IntoCheck: Sized + AsRef + 'static { /// Returns a validated [`Check`] owning `self`. /// /// Note that [`Check`] uses the input of [`IntoCheck::check`] as its backing /// storage, meaning that `"this".check()` will return a `Check`. /// Although this is technically valid, it's _strongly_ recommended to use /// [`Ck`] for string slices instead to avoid confusion. /// /// # Examples /// /// Creating a Unicode ident from a formatted string: /// ```rust /// use strck_ident::{Check, Ck, IntoCheck, unicode::UnicodeIdent}; /// /// fn wrapper_name(name: &Ck) -> Check { /// format!("lil_{name}").check().unwrap() /// } /// ``` fn check(self) -> Result, I::Error>; } impl + 'static> IntoCheck for T { fn check(self) -> Result, I::Error> { Check::from_buf(self) } } // impl Check impl> Check { /// Returns an `Ok` if the buffer upholds the invariants, otherwise `Err`. pub fn from_buf(buf: B) -> Result { I::check(buf.as_ref())?; // SAFETY: invariants are upheld. unsafe { Ok(Self::from_buf_unchecked(buf)) } } /// Create a new [`Check`] without validating the buffer. /// /// # Safety /// /// The buffer must contain a valid string. pub unsafe fn from_buf_unchecked(buf: B) -> Self { Check { _marker: marker::PhantomData, buf, } } /// Returns a [`&Ck`](Ck) that borrows from `self`. pub fn as_ck(&self) -> &Ck { // SAFETY: `self` has the same invariants as `&Ck`. unsafe { Ck::from_str_unchecked(self.buf.as_ref()) } } /// Returns the inner representation. pub fn into_inner(self) -> B { self.buf } } impl fmt::Debug for Check where I: Invariant, B: AsRef + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.buf, f) } } impl PartialEq> for Check where I: Invariant, B1: AsRef, B2: AsRef, { fn eq(&self, other: &Check) -> bool { self == other } } impl PartialOrd> for Check where I: Invariant, B1: AsRef, B2: AsRef, { fn partial_cmp(&self, other: &Check) -> Option { self.as_ck().partial_cmp(other.as_ck()) } } impl> Eq for Check {} impl> Ord for Check { fn cmp(&self, other: &Self) -> cmp::Ordering { self.as_ck().cmp(other.as_ck()) } } impl> hash::Hash for Check { fn hash(&self, state: &mut H) { self.as_str().hash(state); } } impl> ops::Deref for Check { type Target = Ck; fn deref(&self) -> &Self::Target { self.as_ck() } } impl> AsRef> for Check { fn as_ref(&self) -> &Ck { self.as_ck() } } impl> AsRef for Check { fn as_ref(&self) -> &str { self.as_str() } } impl> borrow::Borrow> for Check { fn borrow(&self) -> &Ck { self.as_ck() } } impl> fmt::Display for Check { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl<'a, I, B> From<&'a Ck> for Check where I: Invariant, B: AsRef + From<&'a str>, { fn from(check: &'a Ck) -> Self { check.to_check() } } impl str::FromStr for Check where I: Invariant, for<'a> B: AsRef + From<&'a str>, { type Err = I::Error; fn from_str(s: &str) -> Result { Ok(s.ck()?.to_check()) } } // impl Ck impl Ck { /// Returns an `Ok` if the `&str` upholds the invariants, otherwise `Err`. pub fn from_slice(slice: &str) -> Result<&Self, I::Error> { I::check(slice)?; // SAFETY: invariants are upheld. unsafe { Ok(Self::from_str_unchecked(slice)) } } /// Create a new [`&Ck`](Ck) without validating the `&str`. /// /// # Safety /// /// The string must be valid. pub unsafe fn from_str_unchecked(slice: &str) -> &Self { // SAFETY: `Ck` has the same ABI as `str` by `#[repr(transparent)]`. core::mem::transmute(slice) } /// Returns an owned [`Check`] from `&self`. pub fn to_check<'a, B>(&'a self) -> Check where B: AsRef + From<&'a str>, { // SAFETY: `self` has the same invariants as `Check`. unsafe { Check::from_buf_unchecked(self.as_str().into()) } } /// Returns the `&str` representation. pub fn as_str(&self) -> &str { &self.slice } } impl fmt::Debug for Ck { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.slice, f) } } impl PartialEq for Ck { fn eq(&self, other: &Self) -> bool { self.as_str() == other.as_str() } } impl PartialOrd for Ck { fn partial_cmp(&self, other: &Self) -> Option { self.slice.partial_cmp(&other.slice) } } impl Eq for Ck {} impl Ord for Ck { fn cmp(&self, other: &Self) -> cmp::Ordering { self.as_str().cmp(other.as_str()) } } impl hash::Hash for Ck { fn hash(&self, state: &mut H) { self.as_str().hash(state); } } impl AsRef for Ck { fn as_ref(&self) -> &str { self.as_str() } } impl borrow::Borrow for Ck { fn borrow(&self) -> &str { self.as_str() } } impl ToOwned for Ck { type Owned = Check; fn to_owned(&self) -> Self::Owned { self.to_check() } } impl fmt::Display for Ck { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl<'a, I: Invariant, B: AsRef> From<&'a Check> for &'a Ck { fn from(check: &'a Check) -> Self { check.as_ck() } } impl<'a, I: Invariant> TryFrom<&'a str> for &'a Ck { type Error = I::Error; fn try_from(slice: &'a str) -> Result { Ck::from_slice(slice) } } #[cfg(test)] mod tests { use super::*; /// Test invariant. struct NoInvariant; impl Invariant for NoInvariant { type Error = core::convert::Infallible; fn check(_slice: &str) -> Result<(), Self::Error> { Ok(()) } } #[test] fn test_debug_impl() { let this = "this".ck::().unwrap(); let fmt_debug = format!("{:?}", this); assert_eq!(fmt_debug, "\"this\""); } }