refinement_types/
empty.rs

1//! Predicates based on emptiness.
2
3use core::fmt;
4use core::marker::PhantomData;
5
6use thiserror::Error;
7
8use crate::{core::Predicate, logic::Not, static_str::StaticStr};
9
10/// Represents types that have emptiness-checking capabilities.
11pub trait HasEmpty {
12    /// Checks whether the value is empty.
13    fn empty(&self) -> bool;
14}
15
16/// Represents errors that occur when the provided value is non-empty.
17#[derive(Debug, Error, Default)]
18#[error("received non-empty value")]
19pub struct EmptyError;
20
21impl EmptyError {
22    /// Constructs [`Self`].
23    pub const fn new() -> Self {
24        Self
25    }
26}
27
28/// The `empty value` literal.
29pub const VALUE: StaticStr = "empty value";
30
31/// The `empty` literal.
32pub const EMPTY: StaticStr = "empty";
33
34/// Checks whether the value is empty.
35pub struct Empty {
36    private: PhantomData<()>,
37}
38
39impl<T: HasEmpty + ?Sized> Predicate<T> for Empty {
40    type Error = EmptyError;
41
42    fn check(value: &T) -> Result<(), Self::Error> {
43        if value.empty() {
44            Ok(())
45        } else {
46            Err(Self::Error::new())
47        }
48    }
49
50    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51        formatter.write_str(VALUE)
52    }
53
54    fn expect_code(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55        formatter.write_str(EMPTY)
56    }
57}
58
59/// Checks whether the value is non-empty.
60pub type NonEmpty = Not<Empty>;
61
62// core
63
64impl HasEmpty for str {
65    fn empty(&self) -> bool {
66        self.is_empty()
67    }
68}
69
70impl<T> HasEmpty for [T] {
71    fn empty(&self) -> bool {
72        self.is_empty()
73    }
74}
75
76impl<T: HasEmpty + ?Sized> HasEmpty for &T {
77    fn empty(&self) -> bool {
78        T::empty(self)
79    }
80}
81
82// prelude imports
83
84#[cfg(feature = "alloc")]
85use alloc::{boxed::Box, string::String, vec::Vec};
86
87#[cfg(any(feature = "alloc", feature = "std"))]
88impl<T: HasEmpty + ?Sized> HasEmpty for Box<T> {
89    fn empty(&self) -> bool {
90        T::empty(self)
91    }
92}
93
94#[cfg(any(feature = "alloc", feature = "std"))]
95impl HasEmpty for String {
96    fn empty(&self) -> bool {
97        self.is_empty()
98    }
99}
100
101#[cfg(any(feature = "alloc", feature = "std"))]
102impl<T> HasEmpty for Vec<T> {
103    fn empty(&self) -> bool {
104        self.is_empty()
105    }
106}
107
108// clone-on-write
109
110#[cfg(feature = "alloc")]
111use alloc::borrow::{Cow, ToOwned};
112
113#[cfg(all(not(feature = "alloc"), feature = "std"))]
114use std::borrow::{Cow, ToOwned};
115
116#[cfg(any(feature = "alloc", feature = "std"))]
117impl<T: ToOwned + HasEmpty + ?Sized> HasEmpty for Cow<'_, T> {
118    fn empty(&self) -> bool {
119        T::empty(self)
120    }
121}
122
123// pointers
124
125#[cfg(feature = "alloc")]
126use alloc::rc::Rc;
127
128#[cfg(all(not(feature = "alloc"), feature = "std"))]
129use std::rc::Rc;
130
131#[cfg(any(feature = "alloc", feature = "std"))]
132impl<T: HasEmpty + ?Sized> HasEmpty for Rc<T> {
133    fn empty(&self) -> bool {
134        T::empty(self)
135    }
136}
137
138#[cfg(feature = "alloc")]
139use alloc::sync::Arc;
140
141#[cfg(all(not(feature = "alloc"), feature = "std"))]
142use std::sync::Arc;
143
144#[cfg(any(feature = "alloc", feature = "std"))]
145impl<T: HasEmpty + ?Sized> HasEmpty for Arc<T> {
146    fn empty(&self) -> bool {
147        T::empty(self)
148    }
149}
150
151// shared collections
152
153#[cfg(feature = "alloc")]
154use alloc::collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque};
155
156#[cfg(all(not(feature = "alloc"), feature = "std"))]
157use std::collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque};
158
159#[cfg(any(feature = "alloc", feature = "std"))]
160impl<K, V> HasEmpty for BTreeMap<K, V> {
161    fn empty(&self) -> bool {
162        self.is_empty()
163    }
164}
165
166#[cfg(any(feature = "alloc", feature = "std"))]
167impl<T> HasEmpty for BTreeSet<T> {
168    fn empty(&self) -> bool {
169        self.is_empty()
170    }
171}
172
173#[cfg(any(feature = "alloc", feature = "std"))]
174impl<T> HasEmpty for BinaryHeap<T> {
175    fn empty(&self) -> bool {
176        self.is_empty()
177    }
178}
179
180#[cfg(any(feature = "alloc", feature = "std"))]
181impl<T> HasEmpty for LinkedList<T> {
182    fn empty(&self) -> bool {
183        self.is_empty()
184    }
185}
186
187#[cfg(any(feature = "alloc", feature = "std"))]
188impl<T> HasEmpty for VecDeque<T> {
189    fn empty(&self) -> bool {
190        self.is_empty()
191    }
192}
193
194// collections
195
196#[cfg(feature = "std")]
197use std::collections::{HashMap, HashSet};
198
199#[cfg(feature = "std")]
200impl<K, V, S> HasEmpty for HashMap<K, V, S> {
201    fn empty(&self) -> bool {
202        self.is_empty()
203    }
204}
205
206#[cfg(feature = "std")]
207impl<T, S> HasEmpty for HashSet<T, S> {
208    fn empty(&self) -> bool {
209        self.is_empty()
210    }
211}
212
213// OS strings
214
215#[cfg(feature = "std")]
216use std::ffi::{OsStr, OsString};
217
218#[cfg(feature = "std")]
219impl HasEmpty for OsStr {
220    fn empty(&self) -> bool {
221        self.is_empty()
222    }
223}
224
225#[cfg(feature = "std")]
226impl HasEmpty for OsString {
227    fn empty(&self) -> bool {
228        self.is_empty()
229    }
230}
231
232// paths (via underlying strings)
233
234#[cfg(feature = "std")]
235use std::path::{Path, PathBuf};
236
237#[cfg(feature = "std")]
238impl HasEmpty for Path {
239    fn empty(&self) -> bool {
240        self.as_os_str().is_empty()
241    }
242}
243
244#[cfg(feature = "std")]
245impl HasEmpty for PathBuf {
246    fn empty(&self) -> bool {
247        self.as_os_str().is_empty()
248    }
249}