refinement_types/
empty.rs

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