strck/
lib.rs

1//! [![github-img]][github-url] [![crates-img]][crates-url] [![docs-img]][docs-url]
2//!
3//! [github-url]: https://github.com/QnnOkabayashi/strck
4//! [crates-url]: https://crates.io/crates/strck
5//! [docs-url]: crate
6//! [github-img]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
7//! [crates-img]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
8//! [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
9//!
10//! Checked owned and borrowed strings.
11//!
12//! # Overview
13//!
14//! The Rust standard library provides the `String` and `str` types, which wrap
15//! `Vec<u8>` and `[u8]` respectively, with the invariant that the contents
16//! are valid UTF-8.
17//!
18//! This crate abstracts the idea of type-level invariants on strings by
19//! introducing the immutable [`Check`] and [`Ck`] types, where the invariants
20//! are determined by a generic [`Invariant`] type parameter. It offers
21//! [`UnicodeIdent`](crate::ident::unicode::UnicodeIdent)
22//! and [`RustIdent`](crate::ident::rust::RustIdent) [`Invariant`]s,
23//! which are enabled by the `ident` feature flag.
24//!
25//! "strck" comes from "str check", similar to how rustc has typeck and
26//! borrowck for type check and borrow check respectively.
27//!
28//! # Motivation
29//!
30//! Libraries working with string-like types with certain properties, like identifiers,
31//! quickly become confusing as `&str` and `String` begin to pollute type signatures
32//! everywhere. One solution is to manually implement an owned checked string type
33//! like [`syn::Ident`] to disambiguate the type signatures and validate the string.
34//! The downside is that new values cannot be created without allocation,
35//! which is unnecessary when only a borrowed version is required.
36//!
37//! `strck` solves this issue by providing a checked borrowed string type, [`Ck`],
38//! alongside a checked owned string type, [`Check`]. These serve as thin wrappers
39//! around `str` and `String`[^1] respectively, and prove at the type level that
40//! the contents satisfy the [`Invariant`] that the wrapper is generic over.
41//!
42//! [^1]: [`Check`] can actually be backed by any `'static + AsRef<str>` type,
43//! but `String` is the default.
44//!
45//! # Use cases
46//!
47//! ### Checked strings without allocating
48//!
49//! The main benefit `strck` offers is validating borrowed strings via the
50//! [`Ck`] type without having to allocate in the result.
51//!
52//! ```rust
53//! use strck::{Ck, IntoCk, ident::rust::RustIdent};
54//!
55//! let this_ident: &Ck<RustIdent> = "this".ck().unwrap();
56//! ```
57//!
58//! ### Checked zero-copy deserialization
59//!
60//! When the `serde` feature flag is enabled, [`Ck`]s can be used to perform
61//! checked zero-copy deserialization, which requires the
62//! [`#[serde(borrow)]`][borrow] attribute.
63//!
64//! ```rust
65//! # use serde::{Serialize, Deserialize};
66//! use strck::{Ck, ident::unicode::UnicodeIdent};
67//!
68//! #[derive(Serialize, Deserialize)]
69//! struct Player<'a> {
70//!     #[serde(borrow)]
71//!     username: &'a Ck<UnicodeIdent>,
72//!     level: u32,
73//! }
74//! ```
75//!
76//! Note that this code sample explicitly uses `Ck<UnicodeIdent>` to demonstrate
77//! that the type is a [`Ck`]. However, `strck` provides [`Ident`] as an
78//! alias for `Ck<UnicodeIdent>`, which should be used in practice.
79//!
80//! ### Infallible parsing
81//!
82//! For types where string validation is relatively cheap but parsing is costly
83//! and fallible, `strck` can be used with a custom [`Invariant`] as an input to
84//! make an infallible parsing function.
85//!
86//! # Postfix construction with `IntoCk` and `IntoCheck`
87//!
88//! This crate exposes two helper traits, [`IntoCk`] and [`IntoCheck`]. When in
89//! scope, the [`.ck()`] and [`.check()`] functions can be used to create
90//! [`Ck`]s and [`Check`]s respectively:
91//!
92//! ```rust
93//! use strck::{IntoCheck, IntoCk, ident::unicode::UnicodeIdent};
94//!
95//! let this_ident = "this".ck::<UnicodeIdent>().unwrap();
96//! let this_foo_ident = format!("{}_foo", this_ident).check::<UnicodeIdent>().unwrap();
97//! ```
98//!
99//! # Feature flags
100//!
101//! * `serde`: Implements `Serialize`/`Deserialize` for [`Check`]s and [`Ck`]s,
102//! where the invariants are checked during deserialization. Disabled by default.
103//!
104//! [`syn::Ident`]: https://docs.rs/syn/latest/syn/struct.Ident.html
105//! [`Ident`]: https://docs.rs/strck/latest/strck/ident/unicode/type.Ident.html
106//! [borrow]: https://serde.rs/lifetimes.html#borrowing-data-in-a-derived-impl
107//! [`.ck()`]: IntoCk::ck
108//! [`.check()`]: IntoCheck::check
109use core::{borrow, cmp, fmt, hash, marker, ops, str};
110
111#[cfg(feature = "ident")]
112pub mod ident;
113mod partial_eq;
114#[cfg(feature = "serde")]
115mod serde;
116
117/// Owned immutable string with invariants.
118///
119/// Similar to how `String` derefs to `&str`, [`Check`] derefs to [`&Ck`](Ck).
120/// This means APIs requiring `&Check<I>` as an argument should instead consider
121/// accepting `&Ck<I>` for more flexibility.
122///
123/// # Buffers
124///
125/// By default, this type is backed by a `String`, but it can also be backed by
126/// any `AsRef<str> + 'static` type. In particular, types like [`SmolStr`] are
127/// good candidates since they're designed to be immutable.
128///
129/// It's recommended to use a type alias when using a custom backing type, since
130/// extra generics can make the type signature long.
131///
132/// [`SmolStr`]: https://docs.rs/smol_str/latest/smol_str/struct.SmolStr.html
133#[derive(Clone)]
134#[repr(transparent)]
135pub struct Check<I: Invariant, B: AsRef<str> + 'static = String> {
136    _marker: marker::PhantomData<I>,
137    buf: B,
138}
139
140/// Borrowed immutable string with invariants.
141///
142/// [`Ck`] is a DST, and therefore must always live behind a pointer. This means
143/// you'll usually see it as `&Ck<I>` in type signatures.
144///
145/// # Deserialization
146///
147/// See the [crate-level documentation] for details on how to use [`Ck`] for
148/// checked zero-copy deserialization.
149///
150/// [crate-level documentation]: crate#checked-zero-copy-deserialization
151#[repr(transparent)]
152pub struct Ck<I: Invariant> {
153    _marker: marker::PhantomData<I>,
154    slice: str,
155}
156
157/// Invariant for a [`Ck`] or [`Check`].
158///
159/// The [`Ck`] and [`Check`] types are checked strings types that make guarantees
160/// about the contents of the string. These guarantees are determined by this
161/// trait, `Invariant` which distinguishes whether or not a string upholds some
162/// arbitrary invariants via the [`Invariant::check`] function. If the `Err` is
163/// returned, then the invariant is broken, and the `Ck` or `Check` generic over
164/// the invariant cannot be constructed.
165///
166/// # Examples
167///
168/// Declaring an invariant that the string contains no whitespace:
169/// ```rust
170/// # use strck::Invariant;
171/// struct NoWhitespace;
172///
173/// impl Invariant for NoWhitespace {
174///     type Error = char;
175///
176///     fn check(slice: &str) -> Result<(), Self::Error> {
177///         match slice.chars().find(|ch| ch.is_whitespace()) {
178///             Some(ch) => Err(ch),
179///             None => Ok(()),
180///         }
181///     }
182/// }
183/// ```
184pub trait Invariant: Sized {
185    /// The type returned in the event that an invariant is broken.
186    ///
187    /// When formatting, `Error` should not be capitalized and should not end
188    /// with a period.
189    type Error: fmt::Display;
190
191    /// Returns `Ok` if the string upholds the invariant, otherwise `Err`.
192    ///
193    /// This function is used internally in [`Check::from_buf`] and [`Ck::from_slice`].
194    fn check(slice: &str) -> Result<(), Self::Error>;
195}
196
197/// Conversion into a [`Ck`].
198pub trait IntoCk: Sized + AsRef<str> {
199    /// Returns a validated [`Ck`] borrowing from `self`.
200    ///
201    /// # Examples
202    ///
203    /// Creating an Rust ident containing `this`:
204    /// ```rust
205    /// use strck::{IntoCk, ident::rust::Ident};
206    ///
207    /// let this_ident: &Ident = "this".ck().unwrap();
208    /// ```
209    fn ck<I: Invariant>(&self) -> Result<&Ck<I>, I::Error>;
210}
211
212impl<T: AsRef<str>> IntoCk for T {
213    fn ck<I: Invariant>(&self) -> Result<&Ck<I>, I::Error> {
214        Ck::from_slice(self.as_ref())
215    }
216}
217
218/// Conversion into a [`Check`].
219pub trait IntoCheck: Sized + AsRef<str> + 'static {
220    /// Returns a validated [`Check`] owning `self`.
221    ///
222    /// Note that [`Check`] uses the input of [`IntoCheck::check`] as its backing
223    /// storage, meaning that `"this".check()` will return a `Check<I, &'static str>`.
224    /// Although this is technically valid, it's _strongly_ recommended to use
225    /// [`Ck`] for string slices instead to avoid confusion.
226    ///
227    /// # Examples
228    ///
229    /// Creating a Unicode ident from a formatted string:
230    /// ```rust
231    /// use strck::{Check, Ck, IntoCheck, ident::unicode::UnicodeIdent};
232    ///
233    /// fn wrapper_name(name: &Ck<UnicodeIdent>) -> Check<UnicodeIdent> {
234    ///     format!("lil_{name}").check().unwrap()
235    /// }
236    /// ```
237    fn check<I: Invariant>(self) -> Result<Check<I, Self>, I::Error>;
238}
239
240impl<T: AsRef<str> + 'static> IntoCheck for T {
241    fn check<I: Invariant>(self) -> Result<Check<I, Self>, I::Error> {
242        Check::from_buf(self)
243    }
244}
245
246// impl Check
247
248impl<I: Invariant, B: AsRef<str>> Check<I, B> {
249    /// Returns an `Ok` if the buffer upholds the invariants, otherwise `Err`.
250    pub fn from_buf(buf: B) -> Result<Self, I::Error> {
251        I::check(buf.as_ref())?;
252
253        Ok(Check {
254            _marker: marker::PhantomData,
255            buf,
256        })
257    }
258
259    /// Returns a [`&Ck`](Ck) that borrows from `self`.
260    pub fn as_ck(&self) -> &Ck<I> {
261        // SAFETY: `self` has the same invariants as `&Ck<I>`, and `Ck` has the
262        // same ABI as `str` by `#[repr(transparent)]`.
263        unsafe { core::mem::transmute(self.buf.as_ref()) }
264    }
265
266    /// Returns the inner representation.
267    pub fn into_inner(self) -> B {
268        self.buf
269    }
270}
271
272impl<I, B> fmt::Debug for Check<I, B>
273where
274    I: Invariant,
275    B: AsRef<str> + fmt::Debug,
276{
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        fmt::Debug::fmt(&self.buf, f)
279    }
280}
281
282impl<I, B1, B2> PartialEq<Check<I, B2>> for Check<I, B1>
283where
284    I: Invariant,
285    B1: AsRef<str>,
286    B2: AsRef<str>,
287{
288    fn eq(&self, other: &Check<I, B2>) -> bool {
289        self.as_str() == other.as_str()
290    }
291}
292
293impl<I, B1, B2> PartialOrd<Check<I, B2>> for Check<I, B1>
294where
295    I: Invariant,
296    B1: AsRef<str>,
297    B2: AsRef<str>,
298{
299    fn partial_cmp(&self, other: &Check<I, B2>) -> Option<cmp::Ordering> {
300        self.as_ck().partial_cmp(other.as_ck())
301    }
302}
303
304impl<I: Invariant, B: AsRef<str>> Eq for Check<I, B> {}
305
306impl<I: Invariant, B: AsRef<str>> Ord for Check<I, B> {
307    fn cmp(&self, other: &Self) -> cmp::Ordering {
308        self.as_ck().cmp(other.as_ck())
309    }
310}
311
312impl<I: Invariant, B: AsRef<str>> hash::Hash for Check<I, B> {
313    fn hash<H: hash::Hasher>(&self, state: &mut H) {
314        self.as_str().hash(state);
315    }
316}
317
318impl<I: Invariant, B: AsRef<str>> ops::Deref for Check<I, B> {
319    type Target = Ck<I>;
320
321    fn deref(&self) -> &Self::Target {
322        self.as_ck()
323    }
324}
325
326impl<I: Invariant, B: AsRef<str>> AsRef<Ck<I>> for Check<I, B> {
327    fn as_ref(&self) -> &Ck<I> {
328        self.as_ck()
329    }
330}
331
332impl<I: Invariant, B: AsRef<str>> AsRef<str> for Check<I, B> {
333    fn as_ref(&self) -> &str {
334        self.as_str()
335    }
336}
337
338impl<I: Invariant, B: AsRef<str>> borrow::Borrow<Ck<I>> for Check<I, B> {
339    fn borrow(&self) -> &Ck<I> {
340        self.as_ck()
341    }
342}
343
344impl<I: Invariant, B: AsRef<str>> fmt::Display for Check<I, B> {
345    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346        fmt::Display::fmt(self.as_str(), f)
347    }
348}
349
350impl<'a, I, B> From<&'a Ck<I>> for Check<I, B>
351where
352    I: Invariant,
353    B: AsRef<str> + From<&'a str>,
354{
355    fn from(check: &'a Ck<I>) -> Self {
356        check.to_check()
357    }
358}
359
360impl<I, B> str::FromStr for Check<I, B>
361where
362    I: Invariant,
363    for<'a> B: AsRef<str> + From<&'a str>,
364{
365    type Err = I::Error;
366
367    fn from_str(s: &str) -> Result<Self, Self::Err> {
368        Ok(s.ck()?.to_check())
369    }
370}
371
372// impl Ck
373
374impl<I: Invariant> Ck<I> {
375    /// Returns an `Ok` if the `&str` upholds the invariants, otherwise `Err`.
376    pub fn from_slice(slice: &str) -> Result<&Self, I::Error> {
377        I::check(slice)?;
378
379        // SAFETY: invariants are upheld, and `Ck` has the same ABI as `str` by `#[repr(transparent)]`.
380        unsafe { Ok(core::mem::transmute::<&str, &Ck<I>>(slice)) }
381    }
382
383    /// Returns an owned [`Check`] from `&self`.
384    pub fn to_check<'a, B>(&'a self) -> Check<I, B>
385    where
386        B: AsRef<str> + From<&'a str>,
387    {
388        Check {
389            _marker: marker::PhantomData,
390            buf: self.as_str().into(),
391        }
392    }
393
394    /// Returns the `&str` representation.
395    pub fn as_str(&self) -> &str {
396        &self.slice
397    }
398}
399
400impl<I: Invariant> fmt::Debug for Ck<I> {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        fmt::Debug::fmt(&self.slice, f)
403    }
404}
405
406impl<I: Invariant> PartialEq for Ck<I> {
407    fn eq(&self, other: &Self) -> bool {
408        self.as_str() == other.as_str()
409    }
410}
411
412impl<I: Invariant> PartialOrd for Ck<I> {
413    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
414        Some(self.cmp(other))
415    }
416}
417
418impl<I: Invariant> Eq for Ck<I> {}
419
420impl<I: Invariant> Ord for Ck<I> {
421    fn cmp(&self, other: &Self) -> cmp::Ordering {
422        self.as_str().cmp(other.as_str())
423    }
424}
425
426impl<I: Invariant> hash::Hash for Ck<I> {
427    fn hash<H: hash::Hasher>(&self, state: &mut H) {
428        self.as_str().hash(state);
429    }
430}
431
432impl<I: Invariant> AsRef<str> for Ck<I> {
433    fn as_ref(&self) -> &str {
434        self.as_str()
435    }
436}
437
438impl<I: Invariant> borrow::Borrow<str> for Ck<I> {
439    fn borrow(&self) -> &str {
440        self.as_str()
441    }
442}
443
444impl<I: Invariant> ToOwned for Ck<I> {
445    type Owned = Check<I>;
446
447    fn to_owned(&self) -> Self::Owned {
448        self.to_check()
449    }
450}
451
452impl<I: Invariant> fmt::Display for Ck<I> {
453    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
454        fmt::Display::fmt(self.as_str(), f)
455    }
456}
457
458impl<'a, I: Invariant, B: AsRef<str>> From<&'a Check<I, B>> for &'a Ck<I> {
459    fn from(check: &'a Check<I, B>) -> Self {
460        check.as_ck()
461    }
462}
463
464impl<'a, I: Invariant> TryFrom<&'a str> for &'a Ck<I> {
465    type Error = I::Error;
466
467    fn try_from(slice: &'a str) -> Result<Self, Self::Error> {
468        Ck::from_slice(slice)
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    /// Test invariant.
477    struct NoInvariant;
478
479    impl Invariant for NoInvariant {
480        type Error = core::convert::Infallible;
481
482        fn check(_slice: &str) -> Result<(), Self::Error> {
483            Ok(())
484        }
485    }
486
487    #[test]
488    fn test_debug_impl() {
489        let this = "this".ck::<NoInvariant>().unwrap();
490        let fmt_debug = format!("{this:?}");
491
492        assert_eq!(fmt_debug, "\"this\"");
493    }
494
495    #[test]
496    fn test_ck_partial_eq() {
497        let this = "this".ck::<NoInvariant>().unwrap();
498        let still_this = "this".ck::<NoInvariant>().unwrap();
499        let other = "other".ck::<NoInvariant>().unwrap();
500
501        // With other different instance
502        assert_ne!(this, other);
503        assert_ne!(this, &other);
504        assert_ne!(&this, other);
505        assert_ne!(&this, &other);
506
507        // With other equal instance
508        assert_eq!(this, still_this);
509        assert_eq!(this, &still_this);
510        assert_eq!(&this, still_this);
511        assert_eq!(&this, &still_this);
512
513        // With itself
514        assert_eq!(this, this);
515        assert_eq!(this, &this);
516        assert_eq!(&this, this);
517        assert_eq!(&this, &this);
518    }
519
520    #[test]
521    fn test_check_partial_eq() {
522        let this = "this".check::<NoInvariant>().unwrap();
523        let still_this = "this".check::<NoInvariant>().unwrap();
524        let other = "other".check::<NoInvariant>().unwrap();
525
526        // With other different instance
527        assert_ne!(this, other);
528        assert_ne!(this, &other);
529        assert_ne!(&this, other);
530        assert_ne!(&this, &other);
531
532        // With other equal instance
533        assert_eq!(this, still_this);
534        assert_eq!(this, &still_this);
535        assert_eq!(&this, still_this);
536        assert_eq!(&this, &still_this);
537
538        // With itself
539        assert_eq!(this, this);
540        assert_eq!(this, &this);
541        assert_eq!(&this, this);
542        assert_eq!(&this, &this);
543    }
544}