use std::borrow::Cow;
pub trait MaybeEmpty {
fn is_empty(&self) -> bool;
}
impl MaybeEmpty for () {
fn is_empty(&self) -> bool {
true
}
}
impl MaybeEmpty for str {
fn is_empty(&self) -> bool {
str::is_empty(self)
}
}
impl MaybeEmpty for String {
fn is_empty(&self) -> bool {
String::is_empty(self)
}
}
impl MaybeEmpty for [u8] {
fn is_empty(&self) -> bool {
<[u8]>::is_empty(self)
}
}
impl<const N: usize> MaybeEmpty for [u8; N] {
fn is_empty(&self) -> bool {
N == 0
}
}
impl MaybeEmpty for Vec<u8> {
fn is_empty(&self) -> bool {
Vec::is_empty(self)
}
}
impl<T> MaybeEmpty for Cow<'_, T>
where
T: MaybeEmpty + ToOwned + ?Sized,
{
fn is_empty(&self) -> bool {
T::is_empty(self.as_ref())
}
}
impl<T> MaybeEmpty for &T
where
T: MaybeEmpty + ?Sized,
{
fn is_empty(&self) -> bool {
T::is_empty(self)
}
}
macro_rules! never_empty {
($($ty:ty),+ $(,)?) => {$(
impl MaybeEmpty for $ty {
fn is_empty(&self) -> bool {
false
}
}
impl From<$ty> for NonEmpty<$ty> {
fn from(value: $ty) -> Self {
NonEmpty(value)
}
}
)+};
}
never_empty!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
impl<T> MaybeEmpty for Option<T>
where
T: MaybeEmpty,
{
fn is_empty(&self) -> bool {
match self {
Some(value) => value.is_empty(),
None => true,
}
}
}
impl<A, B> MaybeEmpty for (A, B)
where
A: MaybeEmpty,
B: MaybeEmpty,
{
fn is_empty(&self) -> bool {
self.0.is_empty() && self.1.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
#[error("context value carries no caller-supplied data")]
pub struct EmptyError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NonEmpty<T>(T);
impl<T> NonEmpty<T>
where
T: MaybeEmpty,
{
#[must_use = "the proof lives in the returned value"]
pub fn new(value: T) -> Result<Self, EmptyError> {
if value.is_empty() {
Err(EmptyError)
} else {
Ok(Self(value))
}
}
}
impl<T> NonEmpty<T> {
pub fn into_inner(self) -> T {
self.0
}
pub fn get(&self) -> &T {
&self.0
}
#[must_use = "`with` returns the extended context and leaves the receiver unchanged"]
pub fn with<U>(self, tail: U) -> NonEmpty<(T, U)> {
NonEmpty((self.0, tail))
}
}
impl NonEmpty<&'static str> {
#[must_use = "the proof lives in the returned value"]
pub const fn from_static(value: &'static str) -> Self {
assert!(!value.is_empty(), "a non-empty context cannot be empty");
Self(value)
}
}
impl NonEmpty<&'static [u8]> {
#[must_use = "the proof lives in the returned value"]
pub const fn from_static_bytes(value: &'static [u8]) -> Self {
assert!(!value.is_empty(), "a non-empty context cannot be empty");
Self(value)
}
}
#[macro_export]
macro_rules! nonempty {
($value:expr) => {{
const CONTEXT: $crate::NonEmpty<&'static str> = $crate::NonEmpty::from_static($value);
CONTEXT
}};
}
#[macro_export]
macro_rules! nonempty_bytes {
($value:expr) => {{
const CONTEXT: $crate::NonEmpty<&'static [u8]> =
$crate::NonEmpty::from_static_bytes($value);
CONTEXT
}};
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
#[test]
fn empty_shapes_are_empty() {
assert!(().is_empty_ctx());
assert!("".is_empty_ctx());
assert!(String::new().is_empty_ctx());
assert!(b"".as_slice().is_empty_ctx());
assert!([0u8; 0].is_empty_ctx());
assert!(Vec::<u8>::new().is_empty_ctx());
assert!(Cow::<[u8]>::Borrowed(&[]).is_empty_ctx());
assert!(None::<&str>.is_empty_ctx());
assert!(Some("").is_empty_ctx());
assert!(("", "").is_empty_ctx());
assert!(Some(None::<&str>).is_empty_ctx());
assert!((None::<&str>, Some("")).is_empty_ctx());
assert!(((), ()).is_empty_ctx());
}
#[test]
fn with_pairs_unchecked_and_nests_left() {
let head = nonempty!("users/email");
assert_eq!(head.with(()).get(), &("users/email", ()));
assert_eq!(head.with("").get(), &("users/email", ""));
assert_eq!(
head.with(String::from("acme")).with(7u32).into_inner(),
(("users/email", String::from("acme")), 7u32)
);
}
#[test]
fn with_needs_no_bound_on_the_tail() {
struct Opaque;
let head = nonempty!("users/email");
let _: NonEmpty<(&str, Opaque)> = head.with(Opaque);
let _: NonEmpty<(&str, NonEmpty<&str>)> = head.with(nonempty!("acme"));
}
#[test]
#[allow(deprecated)]
fn the_old_trait_name_still_resolves() {
fn check<T: crate::IsEmpty>(value: &T) -> bool {
crate::IsEmpty::is_empty(value)
}
struct Never;
impl crate::IsEmpty for Never {
fn is_empty(&self) -> bool {
false
}
}
assert!(check(&""));
assert!(!check(&Never));
assert!(NonEmpty::new(Never).is_ok());
}
#[test]
fn with_needs_no_bound_on_the_head() {
fn bind_row<C>(column: NonEmpty<C>, row: u64) -> NonEmpty<(C, u64)> {
column.with(row)
}
assert_eq!(
bind_row(nonempty!("users/email"), 42).into_inner(),
("users/email", 42u64)
);
}
#[test]
fn integers_convert_without_a_check() {
assert_eq!(NonEmpty::from(0u8).into_inner(), 0u8);
assert_eq!(NonEmpty::from(-1i64).into_inner(), -1i64);
let id: NonEmpty<u128> = 7u128.into();
assert_eq!(id.get(), &7u128);
}
#[test]
fn shapes_carrying_information_are_not_empty() {
assert!(!"users/email".is_empty_ctx());
assert!(!"x".is_empty_ctx());
assert!(!String::from("x").is_empty_ctx());
assert!(!b"raw".as_slice().is_empty_ctx());
assert!(![1u8].is_empty_ctx());
assert!(!vec![1u8].is_empty_ctx());
assert!(!Cow::<[u8]>::Owned(vec![1]).is_empty_ctx());
assert!(!Some("users/email").is_empty_ctx());
assert!(!("users", "email").is_empty_ctx());
assert!(!("", "email").is_empty_ctx());
assert!(!("users", "").is_empty_ctx());
assert!(!(None::<&str>, "email").is_empty_ctx());
assert!(!Some(Some("x")).is_empty_ctx());
}
#[test]
fn integers_are_never_empty() {
assert!(!0u8.is_empty_ctx());
assert!(!0u16.is_empty_ctx());
assert!(!0u32.is_empty_ctx());
assert!(!0u64.is_empty_ctx());
assert!(!0u128.is_empty_ctx());
assert!(!0i8.is_empty_ctx());
assert!(!0i16.is_empty_ctx());
assert!(!0i32.is_empty_ctx());
assert!(!0i64.is_empty_ctx());
assert!(!0i128.is_empty_ctx());
assert!(!7u64.is_empty_ctx());
assert!(!Some(0u64).is_empty_ctx());
}
#[test]
fn references_defer_to_the_referent() {
let owned = String::from("x");
assert!(!<&String as MaybeEmpty>::is_empty(&&owned));
assert!(!<&&String as MaybeEmpty>::is_empty(&&&owned));
let empty = String::new();
assert!(<&String as MaybeEmpty>::is_empty(&&empty));
assert!(!<&[u8; 3] as MaybeEmpty>::is_empty(&b"abc"));
assert!(!<&str as MaybeEmpty>::is_empty(&"abc"));
}
#[test]
fn new_checks_once_at_construction() {
assert_eq!(NonEmpty::new("").unwrap_err(), EmptyError);
assert_eq!(NonEmpty::new(("", "")).unwrap_err(), EmptyError);
assert_eq!(NonEmpty::new(None::<&str>).unwrap_err(), EmptyError);
let context = NonEmpty::new("users/email").unwrap();
assert_eq!(context.get(), &"users/email");
assert_eq!(context.into_inner(), "users/email");
let nested = NonEmpty::new(("users", Some("email"))).unwrap();
assert_eq!(nested.into_inner(), ("users", Some("email")));
}
#[test]
fn from_static_accepts_a_non_empty_literal() {
const CONTEXT: NonEmpty<&'static str> = NonEmpty::from_static("users/email");
assert_eq!(CONTEXT.get(), &"users/email");
assert_eq!(nonempty!("users/email"), CONTEXT);
}
#[test]
#[should_panic(expected = "a non-empty context cannot be empty")]
fn from_static_panics_on_an_empty_string_at_runtime() {
let empty = String::new();
let leaked: &'static str = Box::leak(empty.into_boxed_str());
let _ = NonEmpty::from_static(leaked);
}
#[test]
fn from_static_bytes_accepts_a_non_empty_byte_string() {
const CONTEXT: NonEmpty<&'static [u8]> = NonEmpty::from_static_bytes(b"users/email");
assert_eq!(CONTEXT.get(), &b"users/email".as_slice());
assert_eq!(nonempty_bytes!(b"users/email"), CONTEXT);
}
#[test]
#[should_panic(expected = "a non-empty context cannot be empty")]
fn from_static_bytes_panics_on_empty_bytes_at_runtime() {
let leaked: &'static [u8] = Box::leak(Vec::new().into_boxed_slice());
let _ = NonEmpty::from_static_bytes(leaked);
}
#[test]
fn nonempty_macro_accepts_any_static_constant_expression() {
const TABLE: &str = "users";
assert_eq!(nonempty!(TABLE).get(), &"users");
assert_eq!(
nonempty!(concat!("users", "/", "email")).get(),
&"users/email"
);
}
#[test]
fn cow_is_as_empty_as_its_referent() {
assert!(MaybeEmpty::is_empty(&Cow::<str>::Borrowed("")));
assert!(MaybeEmpty::is_empty(&Cow::<str>::Owned(String::new())));
assert!(!MaybeEmpty::is_empty(&Cow::<str>::Borrowed("x")));
assert!(MaybeEmpty::is_empty(&Cow::<[u8]>::Owned(Vec::new())));
assert!(!MaybeEmpty::is_empty(&Cow::<[u8]>::Owned(vec![1])));
}
#[test]
fn error_is_displayable_and_stable() {
assert_eq!(
EmptyError.to_string(),
"context value carries no caller-supplied data"
);
}
#[quickcheck]
fn new_succeeds_exactly_when_the_string_has_bytes(value: String) -> bool {
NonEmpty::new(value.clone()).is_ok() != value.is_empty()
}
#[quickcheck]
fn new_succeeds_exactly_when_the_bytes_are_non_empty(value: Vec<u8>) -> bool {
NonEmpty::new(value.clone()).is_ok() != value.is_empty()
}
#[quickcheck]
fn option_is_as_empty_as_its_payload(value: Option<String>) -> bool {
let expected = value.as_ref().is_none_or(|inner| inner.is_empty());
MaybeEmpty::is_empty(&value) == expected
}
#[quickcheck]
fn pair_is_empty_only_when_both_sides_are(left: String, right: Vec<u8>) -> bool {
let expected = left.is_empty() && right.is_empty();
MaybeEmpty::is_empty(&(left, right)) == expected
}
trait MaybeEmptyCtx {
fn is_empty_ctx(&self) -> bool;
}
impl<T: MaybeEmpty + ?Sized> MaybeEmptyCtx for T {
fn is_empty_ctx(&self) -> bool {
MaybeEmpty::is_empty(self)
}
}
}