pub struct NonEmpty<T>(/* private fields */);Expand description
A context value proven to carry caller-supplied bytes.
An AEAD associated-data value and a PRF context can both legitimately be empty. For some callers, though, an empty context is a security bug rather than a degenerate case: when one value domain-separates every primitive a field uses, an empty one collapses that separation — equal plaintexts in different fields derive identical index terms, every field shares one derived key, and ciphertexts become transplantable between fields.
NonEmpty lets such a caller demand non-emptiness in a bound, without
forcing the invariant on anyone who wants an empty context. It follows the
NonZero pattern: the check (MaybeEmpty) happens exactly once, at
construction, and after that the type carries the invariant, so an API can
take a NonEmpty<C> instead of re-checking on every use.
NonEmpty<T> is transparent to the vitaminc context traits: wrapping a
value changes nothing about how it is encoded, only what the type promises.
It is transparent to Debug too: unlike Protected,
it does not redact, so {:?} prints the inner value verbatim. Context
values are normally public identifiers ("users/email"), which is why this
is the default — but if a context is derived from sensitive data, wrap it
in a redacting type before proving it non-empty, not after.
§Building one
The rule is: checked once where the type cannot prove non-emptiness, converted freely where it can.
- Literals are checked at compile time with
nonempty!; an empty one fails to compile. - Dynamic values are checked at runtime, once, with
NonEmpty::new. - Integers are never empty, so
Fromconverts them with no check:NonEmpty::from(7u64),7u64.into(). - A proven value is extended with
NonEmpty::with, which pairs it with a tail and checks nothing, because the head already carries bytes.
There is no implicit conversion from a string or byte slice: "" and
"users/email" are the same type, so an API accepting a bare &str
could only downgrade to a runtime check while appearing to promise more.
An API that requires the invariant therefore takes NonEmpty<C> itself,
and the call site states which path it is on:
use vitaminc_protected::{nonempty, EmptyError, NonEmpty};
fn bind<C>(context: NonEmpty<C>) -> NonEmpty<C> {
context
}
// A literal: proven non-empty at compile time, no runtime check.
assert_eq!(bind(nonempty!("users/email")).get(), &"users/email");
// A dynamic value: checked structurally, once, at construction.
let field = String::from("users/email");
assert_eq!(bind(NonEmpty::new(field)?).get(), "users/email");
// An integer: never empty, so no check at all.
assert_eq!(bind(NonEmpty::from(7u64)).get(), &7u64);
// A proven head extended with a call-site value: no second check.
assert_eq!(bind(nonempty!("users/email").with(42u64)).get(), &("users/email", 42u64));
// Nesting carries the invariant through.
assert!(NonEmpty::new(("users", Some("email"))).is_ok());
assert_eq!(NonEmpty::new(("", None::<&str>)).unwrap_err(), EmptyError);§Examples
use vitaminc_protected::{nonempty, EmptyError, NonEmpty};
// Runtime-checked, for dynamic values.
let field = String::from("users/email");
let context = NonEmpty::new(field)?;
assert_eq!(context.get(), "users/email");
assert_eq!(NonEmpty::new(String::new()).unwrap_err(), EmptyError);
// Compile-time-checked, for literals: `nonempty!("")` does not compile.
let context: NonEmpty<&'static str> = nonempty!("users/email");
assert_eq!(context.into_inner(), "users/email");Implementations§
Source§impl<T> NonEmpty<T>where
T: MaybeEmpty,
impl<T> NonEmpty<T>where
T: MaybeEmpty,
Sourcepub fn new(value: T) -> Result<Self, EmptyError>
pub fn new(value: T) -> Result<Self, EmptyError>
Source§impl<T> NonEmpty<T>
impl<T> NonEmpty<T>
Sourcepub fn into_inner(self) -> T
pub fn into_inner(self) -> T
Consumes the wrapper, returning the inner value.
Sourcepub fn with<U>(self, tail: U) -> NonEmpty<(T, U)>
pub fn with<U>(self, tail: U) -> NonEmpty<(T, U)>
Pairs this proven value with tail, keeping the proof and checking
nothing: a pair is empty only when both halves are, so
a head that carries caller bytes makes the pair carry them whatever
the tail is — () or "" included. This is how a fixed context is
extended with a value known only at the call site, a record id say,
without giving up the invariant the head already proved:
use vitaminc_protected::{nonempty, NonEmpty};
let column = nonempty!("users/email");
let row: NonEmpty<(&str, u64)> = column.with(42u64);
assert_eq!(row.get(), &("users/email", 42u64));Neither side is bounded. The head needs no MaybeEmpty because it is
already proven, so a generic NonEmpty<C> extends without C: MaybeEmpty leaking into the caller’s bounds; the tail needs none
because nothing is evaluated on it. Any type is a valid tail: a
downstream context type with no MaybeEmpty impl, an already-encoded
Context, even another NonEmpty. The pair frames it
once, as the tuple would.
use vitaminc_protected::{nonempty, NonEmpty};
struct RecordId(u64); // implements nothing from this crate
let row: NonEmpty<(&str, RecordId)> = nonempty!("users/email").with(RecordId(7));
assert_eq!(row.get().1 .0, 7);The pair encodes exactly as the bare (T, U) would (NonEmpty is
transparent to the context traits), so nonempty!("users/email") .with(42u64) encodes to the same bytes as ("users/email", 42u64).
Chaining nests to the left: a.with(b).with(c) is ((a, b), c),
which encodes differently from (a, (b, c)). To match an existing
tuple layout, pass the whole tail at once: a.with((b, c)). For a
layout with the fixed part on the right, build the tuple and prove it
with NonEmpty::new; with only extends rightwards.
Three things the tail does not get from the head:
- It is not checked. If an empty tail would be a bug in your domain, an id that must be present say, validate it before pairing; integer ids need no validation because they are never empty.
- An empty tail does not vanish:
head.with(())is still a pair and is framed as one, so it does not encode to the same bytes asheadalone. Do not use an empty tail to mean “absent” — in AEAD(),""and an emptyVec<u8>all frame to the same zero bytes. - Its type is not authenticated in AEAD: integers encode as raw
little-endian bytes with no tag, so
with(42u64)andwith(42i64)produce the same AAD. See the integer note onIntoAad.
Source§impl NonEmpty<&'static str>
impl NonEmpty<&'static str>
Sourcepub const fn from_static(value: &'static str) -> Self
pub const fn from_static(value: &'static str) -> Self
Wraps a static string, checking at compile time when evaluated in a
const context. Prefer nonempty!, which does
that for you.
§Panics
Panics if value is empty. In the initialiser of a const item that
panic is a compile error, which is the point.
At runtime it is a real panic, so use NonEmpty::new for values that
are not literals.
Source§impl NonEmpty<&'static [u8]>
impl NonEmpty<&'static [u8]>
Sourcepub const fn from_static_bytes(value: &'static [u8]) -> Self
pub const fn from_static_bytes(value: &'static [u8]) -> Self
Wraps a static byte string, checking at compile time when evaluated in
a const context. Prefer nonempty_bytes!,
which does that for you.
§Panics
Panics if value is empty. In the initialiser of a const item that
panic is a compile error, which is the point.
At runtime it is a real panic, so use NonEmpty::new for values that
are not literals.
Trait Implementations§
impl<T: Copy> Copy for NonEmpty<T>
impl<T: Eq> Eq for NonEmpty<T>
Source§impl From<i8> for NonEmpty<i8>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<i8> for NonEmpty<i8>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<i16> for NonEmpty<i16>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<i16> for NonEmpty<i16>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<i32> for NonEmpty<i32>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<i32> for NonEmpty<i32>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<i64> for NonEmpty<i64>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<i64> for NonEmpty<i64>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<i128> for NonEmpty<i128>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<i128> for NonEmpty<i128>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<u8> for NonEmpty<u8>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<u8> for NonEmpty<u8>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<u16> for NonEmpty<u16>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<u16> for NonEmpty<u16>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<u32> for NonEmpty<u32>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<u32> for NonEmpty<u32>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<u64> for NonEmpty<u64>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<u64> for NonEmpty<u64>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
Source§impl From<u128> for NonEmpty<u128>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.
impl From<u128> for NonEmpty<u128>
An integer is never empty, so it converts without a check —
NonEmpty::from(7u64) or 7u64.into() — where a string would
need NonEmpty::new or nonempty!.