vitaminc_protected/non_empty.rs
1//! Non-empty context values. This module is private; its documentation lives
2//! on [`MaybeEmpty`] (what "empty" means) and [`NonEmpty`] (why, and how to
3//! build one).
4
5use std::borrow::Cow;
6
7/// Structural emptiness of a value, decided **before** any encoding is applied.
8///
9/// A type implementing `MaybeEmpty` is one that can be *asked* whether a given
10/// value is empty, not one whose values are empty: integers implement it and
11/// are never empty. It is the bound [`NonEmpty::new`] checks against.
12///
13/// A value is empty when it carries no caller-supplied bytes:
14///
15/// | value | empty when |
16/// |---|---|
17/// | `()` | always |
18/// | `str`, `String`, `[u8]`, `[u8; N]`, `Vec<u8>` | `len() == 0` |
19/// | `Cow<T>` | its referent is empty |
20/// | integers | never — a number is caller information |
21/// | `Option<T>` | `None`, or `Some(t)` with `t` empty |
22/// | `(A, B)` | **both** components empty |
23///
24/// A composite is empty only when it contributes nothing: `("", "email")`
25/// still separates from `("", "id")`, so it is not empty, whereas `("", "")`,
26/// `Some("")` and `Some(None)` are. This is the definition a downstream crate
27/// would otherwise have to reconstruct by parsing the encoded bytes.
28///
29/// [`NonEmpty<T>`] deliberately does **not** implement `MaybeEmpty`, so
30/// [`NonEmpty::new`] checks a composite as a whole
31/// (`NonEmpty::new(("tenant", "email"))`), never a proven part nested inside
32/// a larger value — that would force the outer check to be re-derived anyway.
33/// Extending a proven value is [`NonEmpty::with`]'s job, and it checks
34/// nothing, so its tail needs no `MaybeEmpty` impl at all.
35///
36/// Because the check runs before encoding, *already-encoded* contexts (an
37/// encoded `Context`) do not implement `MaybeEmpty` either: framing makes
38/// their bytes non-empty even when built from an empty value, so an encoded
39/// byte check would certify exactly the degenerate case `NonEmpty` exists to
40/// exclude. Check the value on its way *into* vitaminc, before it is framed.
41///
42/// Implement this for your own context types so they can be wrapped in
43/// [`NonEmpty`].
44pub trait MaybeEmpty {
45 /// Returns `true` if this value carries no caller-supplied bytes.
46 fn is_empty(&self) -> bool;
47}
48
49impl MaybeEmpty for () {
50 fn is_empty(&self) -> bool {
51 true
52 }
53}
54
55impl MaybeEmpty for str {
56 fn is_empty(&self) -> bool {
57 str::is_empty(self)
58 }
59}
60
61impl MaybeEmpty for String {
62 fn is_empty(&self) -> bool {
63 String::is_empty(self)
64 }
65}
66
67impl MaybeEmpty for [u8] {
68 fn is_empty(&self) -> bool {
69 <[u8]>::is_empty(self)
70 }
71}
72
73impl<const N: usize> MaybeEmpty for [u8; N] {
74 fn is_empty(&self) -> bool {
75 N == 0
76 }
77}
78
79impl MaybeEmpty for Vec<u8> {
80 fn is_empty(&self) -> bool {
81 Vec::is_empty(self)
82 }
83}
84
85/// A `Cow` is as empty as its referent, whichever side it holds.
86impl<T> MaybeEmpty for Cow<'_, T>
87where
88 T: MaybeEmpty + ToOwned + ?Sized,
89{
90 fn is_empty(&self) -> bool {
91 T::is_empty(self.as_ref())
92 }
93}
94
95impl<T> MaybeEmpty for &T
96where
97 T: MaybeEmpty + ?Sized,
98{
99 fn is_empty(&self) -> bool {
100 T::is_empty(self)
101 }
102}
103
104macro_rules! never_empty {
105 ($($ty:ty),+ $(,)?) => {$(
106 /// An integer is caller information, so it is never empty.
107 impl MaybeEmpty for $ty {
108 fn is_empty(&self) -> bool {
109 false
110 }
111 }
112
113 /// An integer is never empty, so it converts without a check —
114 /// `NonEmpty::from(7u64)` or `7u64.into()` — where a string would
115 /// need [`NonEmpty::new`] or [`nonempty!`](crate::nonempty).
116 impl From<$ty> for NonEmpty<$ty> {
117 fn from(value: $ty) -> Self {
118 NonEmpty(value)
119 }
120 }
121 )+};
122}
123
124never_empty!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
125
126/// `None` is empty; `Some(value)` is as empty as `value`.
127impl<T> MaybeEmpty for Option<T>
128where
129 T: MaybeEmpty,
130{
131 fn is_empty(&self) -> bool {
132 match self {
133 Some(value) => value.is_empty(),
134 None => true,
135 }
136 }
137}
138
139/// A pair is empty only when **both** components are: a composite that still
140/// contributes caller bytes on either side is not the degenerate case.
141impl<A, B> MaybeEmpty for (A, B)
142where
143 A: MaybeEmpty,
144 B: MaybeEmpty,
145{
146 fn is_empty(&self) -> bool {
147 self.0.is_empty() && self.1.is_empty()
148 }
149}
150
151/// The error returned when a value that must carry caller-supplied data
152/// turned out to be [empty](MaybeEmpty).
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
154#[error("context value carries no caller-supplied data")]
155pub struct EmptyError;
156
157/// A context value proven to carry caller-supplied bytes.
158///
159/// An AEAD associated-data value and a PRF context can both legitimately be
160/// empty. For some callers, though, an empty context is a security bug rather
161/// than a degenerate case: when one value domain-separates every primitive a
162/// field uses, an empty one collapses that separation — equal plaintexts in
163/// different fields derive identical index terms, every field shares one
164/// derived key, and ciphertexts become transplantable between fields.
165///
166/// `NonEmpty` lets such a caller *demand* non-emptiness in a bound, without
167/// forcing the invariant on anyone who wants an empty context. It follows the
168/// `NonZero` pattern: the check ([`MaybeEmpty`]) happens exactly once, at
169/// construction, and after that the type carries the invariant, so an API can
170/// take a `NonEmpty<C>` instead of re-checking on every use.
171///
172/// `NonEmpty<T>` is transparent to the vitaminc context traits: wrapping a
173/// value changes nothing about how it is encoded, only what the type promises.
174///
175/// It is transparent to `Debug` too: unlike [`Protected`](crate::Protected),
176/// it does not redact, so `{:?}` prints the inner value verbatim. Context
177/// values are normally public identifiers (`"users/email"`), which is why this
178/// is the default — but if a context is derived from sensitive data, wrap it
179/// in a redacting type before proving it non-empty, not after.
180///
181/// # Building one
182///
183/// The rule is: checked once where the type cannot prove non-emptiness,
184/// converted freely where it can.
185///
186/// - **Literals** are checked at compile time with
187/// [`nonempty!`](crate::nonempty); an empty one fails to compile.
188/// - **Dynamic values** are checked at runtime, once, with [`NonEmpty::new`].
189/// - **Integers** are never empty, so `From` converts them with no check:
190/// `NonEmpty::from(7u64)`, `7u64.into()`.
191/// - **A proven value** is extended with [`NonEmpty::with`], which pairs it
192/// with a tail and checks nothing, because the head already carries bytes.
193///
194/// There is no implicit conversion from a string or byte slice: `""` and
195/// `"users/email"` are the same type, so an API accepting a bare `&str`
196/// could only downgrade to a runtime check while appearing to promise more.
197/// An API that requires the invariant therefore takes `NonEmpty<C>` itself,
198/// and the call site states which path it is on:
199///
200/// ```rust
201/// use vitaminc_protected::{nonempty, EmptyError, NonEmpty};
202///
203/// fn bind<C>(context: NonEmpty<C>) -> NonEmpty<C> {
204/// context
205/// }
206///
207/// // A literal: proven non-empty at compile time, no runtime check.
208/// assert_eq!(bind(nonempty!("users/email")).get(), &"users/email");
209///
210/// // A dynamic value: checked structurally, once, at construction.
211/// let field = String::from("users/email");
212/// assert_eq!(bind(NonEmpty::new(field)?).get(), "users/email");
213///
214/// // An integer: never empty, so no check at all.
215/// assert_eq!(bind(NonEmpty::from(7u64)).get(), &7u64);
216///
217/// // A proven head extended with a call-site value: no second check.
218/// assert_eq!(bind(nonempty!("users/email").with(42u64)).get(), &("users/email", 42u64));
219///
220/// // Nesting carries the invariant through.
221/// assert!(NonEmpty::new(("users", Some("email"))).is_ok());
222/// assert_eq!(NonEmpty::new(("", None::<&str>)).unwrap_err(), EmptyError);
223/// # Ok::<(), EmptyError>(())
224/// ```
225///
226/// # Examples
227///
228/// ```rust
229/// use vitaminc_protected::{nonempty, EmptyError, NonEmpty};
230///
231/// // Runtime-checked, for dynamic values.
232/// let field = String::from("users/email");
233/// let context = NonEmpty::new(field)?;
234/// assert_eq!(context.get(), "users/email");
235///
236/// assert_eq!(NonEmpty::new(String::new()).unwrap_err(), EmptyError);
237///
238/// // Compile-time-checked, for literals: `nonempty!("")` does not compile.
239/// let context: NonEmpty<&'static str> = nonempty!("users/email");
240/// assert_eq!(context.into_inner(), "users/email");
241/// # Ok::<(), EmptyError>(())
242/// ```
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
244pub struct NonEmpty<T>(T);
245
246impl<T> NonEmpty<T>
247where
248 T: MaybeEmpty,
249{
250 /// Wraps `value`, checking once that it is not [empty](MaybeEmpty).
251 ///
252 /// # Errors
253 ///
254 /// Returns [`EmptyError`] if `value.is_empty()`.
255 #[must_use = "the proof lives in the returned value"]
256 pub fn new(value: T) -> Result<Self, EmptyError> {
257 if value.is_empty() {
258 Err(EmptyError)
259 } else {
260 Ok(Self(value))
261 }
262 }
263}
264
265impl<T> NonEmpty<T> {
266 /// Consumes the wrapper, returning the inner value.
267 pub fn into_inner(self) -> T {
268 self.0
269 }
270
271 /// Borrows the inner value.
272 pub fn get(&self) -> &T {
273 &self.0
274 }
275
276 /// Pairs this proven value with `tail`, keeping the proof and checking
277 /// nothing: a pair is [empty](MaybeEmpty) only when **both** halves are, so
278 /// a head that carries caller bytes makes the pair carry them whatever
279 /// the tail is — `()` or `""` included. This is how a fixed context is
280 /// extended with a value known only at the call site, a record id say,
281 /// without giving up the invariant the head already proved:
282 ///
283 /// ```rust
284 /// use vitaminc_protected::{nonempty, NonEmpty};
285 ///
286 /// let column = nonempty!("users/email");
287 /// let row: NonEmpty<(&str, u64)> = column.with(42u64);
288 /// assert_eq!(row.get(), &("users/email", 42u64));
289 /// ```
290 ///
291 /// Neither side is bounded. The head needs no `MaybeEmpty` because it is
292 /// already proven, so a generic `NonEmpty<C>` extends without `C:
293 /// MaybeEmpty` leaking into the caller's bounds; the tail needs none
294 /// because nothing is evaluated on it. Any type is a valid tail: a
295 /// downstream context type with no `MaybeEmpty` impl, an already-encoded
296 /// `Context`, even another `NonEmpty`. The pair frames it
297 /// once, as the tuple would.
298 ///
299 /// ```rust
300 /// use vitaminc_protected::{nonempty, NonEmpty};
301 ///
302 /// struct RecordId(u64); // implements nothing from this crate
303 ///
304 /// let row: NonEmpty<(&str, RecordId)> = nonempty!("users/email").with(RecordId(7));
305 /// assert_eq!(row.get().1 .0, 7);
306 /// ```
307 ///
308 /// The pair encodes exactly as the bare `(T, U)` would (`NonEmpty` is
309 /// transparent to the context traits), so `nonempty!("users/email")
310 /// .with(42u64)` encodes to the same bytes as `("users/email", 42u64)`.
311 /// Chaining nests to the **left**: `a.with(b).with(c)` is `((a, b), c)`,
312 /// which encodes differently from `(a, (b, c))`. To match an existing
313 /// tuple layout, pass the whole tail at once: `a.with((b, c))`. For a
314 /// layout with the fixed part on the *right*, build the tuple and prove it
315 /// with [`NonEmpty::new`]; `with` only extends rightwards.
316 ///
317 /// Three things the tail does **not** get from the head:
318 ///
319 /// - It is not checked. If an empty tail would be a bug in your domain, an
320 /// id that must be present say, validate it before pairing; integer ids
321 /// need no validation because they are never empty.
322 /// - An empty tail does not vanish: `head.with(())` is still a pair and is
323 /// framed as one, so it does not encode to the same bytes as `head`
324 /// alone. Do not use an empty tail to mean "absent" — in AEAD `()`, `""`
325 /// and an empty `Vec<u8>` all frame to the same zero bytes.
326 /// - Its type is not authenticated in AEAD: integers encode as raw
327 /// little-endian bytes with no tag, so `with(42u64)` and `with(42i64)`
328 /// produce the same AAD. See the integer note on `IntoAad`.
329 #[must_use = "`with` returns the extended context and leaves the receiver unchanged"]
330 pub fn with<U>(self, tail: U) -> NonEmpty<(T, U)> {
331 NonEmpty((self.0, tail))
332 }
333}
334
335impl NonEmpty<&'static str> {
336 /// Wraps a static string, checking at compile time when evaluated in a
337 /// `const` context. Prefer [`nonempty!`](crate::nonempty), which does
338 /// that for you.
339 ///
340 /// # Panics
341 ///
342 /// Panics if `value` is empty. In the initialiser of a `const` item that
343 /// panic is a compile error, which is the point.
344 /// At runtime it is a real panic, so use [`NonEmpty::new`] for values that
345 /// are not literals.
346 #[must_use = "the proof lives in the returned value"]
347 pub const fn from_static(value: &'static str) -> Self {
348 assert!(!value.is_empty(), "a non-empty context cannot be empty");
349 Self(value)
350 }
351}
352
353impl NonEmpty<&'static [u8]> {
354 /// Wraps a static byte string, checking at compile time when evaluated in
355 /// a `const` context. Prefer [`nonempty_bytes!`](crate::nonempty_bytes),
356 /// which does that for you.
357 ///
358 /// # Panics
359 ///
360 /// Panics if `value` is empty. In the initialiser of a `const` item that
361 /// panic is a compile error, which is the point.
362 /// At runtime it is a real panic, so use [`NonEmpty::new`] for values that
363 /// are not literals.
364 #[must_use = "the proof lives in the returned value"]
365 pub const fn from_static_bytes(value: &'static [u8]) -> Self {
366 assert!(!value.is_empty(), "a non-empty context cannot be empty");
367 Self(value)
368 }
369}
370
371/// A [`NonEmpty<&'static str>`](NonEmpty) checked at compile time.
372///
373/// Expands to a `const` item, so an empty value fails to compile rather
374/// than panicking at runtime. (A `const { … }` block would not do: its
375/// evaluation is deferred to codegen, so `cargo check` — and any tool built
376/// on it — would not report the error.)
377///
378/// Any `&'static str` constant expression works, not just a literal: a
379/// `const`, or a `concat!`/`env!` composition, stays compile-checked because
380/// the check runs in the `const` item the macro expands to. For byte-string
381/// contexts use [`nonempty_bytes!`](crate::nonempty_bytes).
382///
383/// ```rust
384/// use vitaminc_protected::{nonempty, NonEmpty};
385///
386/// let context: NonEmpty<&'static str> = nonempty!("users/email");
387/// assert_eq!(context.get(), &"users/email");
388///
389/// const TABLE: &str = "users";
390/// let composed = nonempty!(concat!("users", "/", "email"));
391/// let from_const = nonempty!(TABLE);
392/// assert_eq!(composed.get(), &"users/email");
393/// assert_eq!(from_const.get(), &"users");
394/// ```
395///
396/// ```rust,compile_fail
397/// let context = vitaminc_protected::nonempty!("");
398/// ```
399#[macro_export]
400macro_rules! nonempty {
401 ($value:expr) => {{
402 const CONTEXT: $crate::NonEmpty<&'static str> = $crate::NonEmpty::from_static($value);
403 CONTEXT
404 }};
405}
406
407/// A [`NonEmpty<&'static [u8]>`](NonEmpty) checked at compile time.
408///
409/// The byte-string twin of [`nonempty!`](crate::nonempty) — the `Context` derivation
410/// APIs are byte-oriented, so a domain tag that is naturally a
411/// byte string deserves the same compile-time path as a `&str` one, instead
412/// of a runtime `NonEmpty::new(b"tag".as_slice()).unwrap()`.
413///
414/// ```rust
415/// use vitaminc_protected::{nonempty_bytes, NonEmpty};
416///
417/// let context: NonEmpty<&'static [u8]> = nonempty_bytes!(b"users/email");
418/// assert_eq!(context.get(), &b"users/email".as_slice());
419/// ```
420///
421/// ```rust,compile_fail
422/// let context = vitaminc_protected::nonempty_bytes!(b"");
423/// ```
424#[macro_export]
425macro_rules! nonempty_bytes {
426 ($value:expr) => {{
427 const CONTEXT: $crate::NonEmpty<&'static [u8]> =
428 $crate::NonEmpty::from_static_bytes($value);
429 CONTEXT
430 }};
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use quickcheck_macros::quickcheck;
437
438 #[test]
439 fn empty_shapes_are_empty() {
440 assert!(().is_empty_ctx());
441 assert!("".is_empty_ctx());
442 assert!(String::new().is_empty_ctx());
443 assert!(b"".as_slice().is_empty_ctx());
444 assert!([0u8; 0].is_empty_ctx());
445 assert!(Vec::<u8>::new().is_empty_ctx());
446 assert!(Cow::<[u8]>::Borrowed(&[]).is_empty_ctx());
447 assert!(None::<&str>.is_empty_ctx());
448 assert!(Some("").is_empty_ctx());
449 assert!(("", "").is_empty_ctx());
450 assert!(Some(None::<&str>).is_empty_ctx());
451 assert!((None::<&str>, Some("")).is_empty_ctx());
452 assert!(((), ()).is_empty_ctx());
453 }
454
455 #[test]
456 fn with_pairs_unchecked_and_nests_left() {
457 // The head is proven; the tail is not checked, and need not carry
458 // anything — a pair is empty only when both halves are. Chaining
459 // nests to the left, as the rustdoc promises.
460 let head = nonempty!("users/email");
461 assert_eq!(head.with(()).get(), &("users/email", ()));
462 assert_eq!(head.with("").get(), &("users/email", ""));
463 assert_eq!(
464 head.with(String::from("acme")).with(7u32).into_inner(),
465 (("users/email", String::from("acme")), 7u32)
466 );
467 }
468
469 #[test]
470 fn with_needs_no_bound_on_the_tail() {
471 // #313: any tail, no check. A type with no `MaybeEmpty` impl, an
472 // already-encoded context stand-in, and another `NonEmpty` all pass.
473 // Adding `U: MaybeEmpty` to `with` would stop this compiling.
474 struct Opaque;
475 let head = nonempty!("users/email");
476 let _: NonEmpty<(&str, Opaque)> = head.with(Opaque);
477 let _: NonEmpty<(&str, NonEmpty<&str>)> = head.with(nonempty!("acme"));
478 }
479
480 #[test]
481 #[allow(deprecated)]
482 fn the_old_trait_name_still_resolves() {
483 // `IsEmpty` is a deprecated alias for `MaybeEmpty`: 0.2.0 bounds and
484 // impls keep compiling, with a warning that names the replacement.
485 fn check<T: crate::IsEmpty>(value: &T) -> bool {
486 crate::IsEmpty::is_empty(value)
487 }
488 struct Never;
489 impl crate::IsEmpty for Never {
490 fn is_empty(&self) -> bool {
491 false
492 }
493 }
494 assert!(check(&""));
495 assert!(!check(&Never));
496 assert!(NonEmpty::new(Never).is_ok());
497 }
498
499 #[test]
500 fn with_needs_no_bound_on_the_head() {
501 // The point of `with` for a generic consumer: a `NonEmpty<C>` can be
502 // extended without `C: MaybeEmpty` leaking into the caller's bounds.
503 // Adding `T: MaybeEmpty` to `with` would stop this compiling.
504 fn bind_row<C>(column: NonEmpty<C>, row: u64) -> NonEmpty<(C, u64)> {
505 column.with(row)
506 }
507 assert_eq!(
508 bind_row(nonempty!("users/email"), 42).into_inner(),
509 ("users/email", 42u64)
510 );
511 }
512
513 #[test]
514 fn integers_convert_without_a_check() {
515 assert_eq!(NonEmpty::from(0u8).into_inner(), 0u8);
516 assert_eq!(NonEmpty::from(-1i64).into_inner(), -1i64);
517 let id: NonEmpty<u128> = 7u128.into();
518 assert_eq!(id.get(), &7u128);
519 }
520
521 #[test]
522 fn shapes_carrying_information_are_not_empty() {
523 assert!(!"users/email".is_empty_ctx());
524 assert!(!"x".is_empty_ctx());
525 assert!(!String::from("x").is_empty_ctx());
526 assert!(!b"raw".as_slice().is_empty_ctx());
527 assert!(![1u8].is_empty_ctx());
528 assert!(!vec![1u8].is_empty_ctx());
529 assert!(!Cow::<[u8]>::Owned(vec![1]).is_empty_ctx());
530 assert!(!Some("users/email").is_empty_ctx());
531 assert!(!("users", "email").is_empty_ctx());
532 // A composite that still contributes bytes on one side is not the
533 // degenerate case.
534 assert!(!("", "email").is_empty_ctx());
535 assert!(!("users", "").is_empty_ctx());
536 assert!(!(None::<&str>, "email").is_empty_ctx());
537 assert!(!Some(Some("x")).is_empty_ctx());
538 }
539
540 #[test]
541 fn integers_are_never_empty() {
542 assert!(!0u8.is_empty_ctx());
543 assert!(!0u16.is_empty_ctx());
544 assert!(!0u32.is_empty_ctx());
545 assert!(!0u64.is_empty_ctx());
546 assert!(!0u128.is_empty_ctx());
547 assert!(!0i8.is_empty_ctx());
548 assert!(!0i16.is_empty_ctx());
549 assert!(!0i32.is_empty_ctx());
550 assert!(!0i64.is_empty_ctx());
551 assert!(!0i128.is_empty_ctx());
552 assert!(!7u64.is_empty_ctx());
553 assert!(!Some(0u64).is_empty_ctx());
554 }
555
556 #[test]
557 fn references_defer_to_the_referent() {
558 let owned = String::from("x");
559 assert!(!<&String as MaybeEmpty>::is_empty(&&owned));
560 assert!(!<&&String as MaybeEmpty>::is_empty(&&&owned));
561 let empty = String::new();
562 assert!(<&String as MaybeEmpty>::is_empty(&&empty));
563 assert!(!<&[u8; 3] as MaybeEmpty>::is_empty(&b"abc"));
564 assert!(!<&str as MaybeEmpty>::is_empty(&"abc"));
565 }
566
567 #[test]
568 fn new_checks_once_at_construction() {
569 assert_eq!(NonEmpty::new("").unwrap_err(), EmptyError);
570 assert_eq!(NonEmpty::new(("", "")).unwrap_err(), EmptyError);
571 assert_eq!(NonEmpty::new(None::<&str>).unwrap_err(), EmptyError);
572
573 let context = NonEmpty::new("users/email").unwrap();
574 assert_eq!(context.get(), &"users/email");
575 assert_eq!(context.into_inner(), "users/email");
576
577 let nested = NonEmpty::new(("users", Some("email"))).unwrap();
578 assert_eq!(nested.into_inner(), ("users", Some("email")));
579 }
580
581 #[test]
582 fn from_static_accepts_a_non_empty_literal() {
583 const CONTEXT: NonEmpty<&'static str> = NonEmpty::from_static("users/email");
584 assert_eq!(CONTEXT.get(), &"users/email");
585 assert_eq!(nonempty!("users/email"), CONTEXT);
586 }
587
588 #[test]
589 #[should_panic(expected = "a non-empty context cannot be empty")]
590 fn from_static_panics_on_an_empty_string_at_runtime() {
591 let empty = String::new();
592 // Leak so the value is genuinely `'static` yet not a compile-time
593 // constant; the `const fn` runs at runtime here.
594 let leaked: &'static str = Box::leak(empty.into_boxed_str());
595 let _ = NonEmpty::from_static(leaked);
596 }
597
598 #[test]
599 fn from_static_bytes_accepts_a_non_empty_byte_string() {
600 const CONTEXT: NonEmpty<&'static [u8]> = NonEmpty::from_static_bytes(b"users/email");
601 assert_eq!(CONTEXT.get(), &b"users/email".as_slice());
602 assert_eq!(nonempty_bytes!(b"users/email"), CONTEXT);
603 }
604
605 #[test]
606 #[should_panic(expected = "a non-empty context cannot be empty")]
607 fn from_static_bytes_panics_on_empty_bytes_at_runtime() {
608 let leaked: &'static [u8] = Box::leak(Vec::new().into_boxed_slice());
609 let _ = NonEmpty::from_static_bytes(leaked);
610 }
611
612 #[test]
613 fn nonempty_macro_accepts_any_static_constant_expression() {
614 const TABLE: &str = "users";
615 assert_eq!(nonempty!(TABLE).get(), &"users");
616 assert_eq!(
617 nonempty!(concat!("users", "/", "email")).get(),
618 &"users/email"
619 );
620 }
621
622 #[test]
623 fn cow_is_as_empty_as_its_referent() {
624 assert!(MaybeEmpty::is_empty(&Cow::<str>::Borrowed("")));
625 assert!(MaybeEmpty::is_empty(&Cow::<str>::Owned(String::new())));
626 assert!(!MaybeEmpty::is_empty(&Cow::<str>::Borrowed("x")));
627 assert!(MaybeEmpty::is_empty(&Cow::<[u8]>::Owned(Vec::new())));
628 assert!(!MaybeEmpty::is_empty(&Cow::<[u8]>::Owned(vec![1])));
629 }
630
631 #[test]
632 fn error_is_displayable_and_stable() {
633 assert_eq!(
634 EmptyError.to_string(),
635 "context value carries no caller-supplied data"
636 );
637 }
638
639 #[quickcheck]
640 fn new_succeeds_exactly_when_the_string_has_bytes(value: String) -> bool {
641 NonEmpty::new(value.clone()).is_ok() != value.is_empty()
642 }
643
644 #[quickcheck]
645 fn new_succeeds_exactly_when_the_bytes_are_non_empty(value: Vec<u8>) -> bool {
646 NonEmpty::new(value.clone()).is_ok() != value.is_empty()
647 }
648
649 #[quickcheck]
650 fn option_is_as_empty_as_its_payload(value: Option<String>) -> bool {
651 let expected = value.as_ref().is_none_or(|inner| inner.is_empty());
652 MaybeEmpty::is_empty(&value) == expected
653 }
654
655 #[quickcheck]
656 fn pair_is_empty_only_when_both_sides_are(left: String, right: Vec<u8>) -> bool {
657 let expected = left.is_empty() && right.is_empty();
658 MaybeEmpty::is_empty(&(left, right)) == expected
659 }
660
661 /// Disambiguates from the inherent `is_empty` on `str`, `String`, `Vec`
662 /// and slices so every assertion above exercises the trait.
663 trait MaybeEmptyCtx {
664 fn is_empty_ctx(&self) -> bool;
665 }
666
667 impl<T: MaybeEmpty + ?Sized> MaybeEmptyCtx for T {
668 fn is_empty_ctx(&self) -> bool {
669 MaybeEmpty::is_empty(self)
670 }
671 }
672}