refinement_types/
empty.rs

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