refinement_types/
str.rs

1//! Predicates for strings.
2
3use core::{fmt, marker::PhantomData};
4
5use thiserror::Error;
6
7use crate::{core::Predicate, static_str::StaticStr, type_str::TypeStr};
8
9#[cfg(feature = "regex")]
10use crate::type_regex::TypeRegex;
11
12/// Represents errors that occur when the string does not start with [`prefix`].
13///
14/// [`prefix`]: Self::prefix
15#[derive(Debug, Error)]
16#[error("expected string to start with `{prefix}`")]
17pub struct StartsWithError {
18    /// The expected prefix.
19    pub prefix: StaticStr,
20}
21
22impl StartsWithError {
23    /// Constructs [`Self`].
24    pub const fn new(prefix: StaticStr) -> Self {
25        Self { prefix }
26    }
27}
28
29/// Checks if the string starts with the specified prefix `S`.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
31pub struct StartsWith<S: TypeStr + ?Sized> {
32    prefix: PhantomData<S>,
33}
34
35impl<T: AsRef<str> + ?Sized, S: TypeStr + ?Sized> Predicate<T> for StartsWith<S> {
36    type Error = StartsWithError;
37
38    fn check(value: &T) -> Result<(), Self::Error> {
39        if value.as_ref().starts_with(S::VALUE) {
40            Ok(())
41        } else {
42            Err(Self::Error::new(S::VALUE))
43        }
44    }
45
46    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(
48            formatter,
49            "string starting with `{prefix}`",
50            prefix = S::VALUE
51        )
52    }
53}
54
55/// Represents errors that occur when the string does not end with [`suffix`].
56///
57/// [`suffix`]: Self::suffix
58#[derive(Debug, Error)]
59#[error("expected string to end with `{suffix}`")]
60pub struct EndsWithError {
61    /// The expected suffix.
62    pub suffix: StaticStr,
63}
64
65impl EndsWithError {
66    /// Constructs [`Self`].
67    pub const fn new(suffix: StaticStr) -> Self {
68        Self { suffix }
69    }
70}
71
72/// Checks if the string ends with the specified suffix `S`.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
74pub struct EndsWith<S: TypeStr + ?Sized> {
75    suffix: PhantomData<S>,
76}
77
78impl<T: AsRef<str> + ?Sized, S: TypeStr + ?Sized> Predicate<T> for EndsWith<S> {
79    type Error = EndsWithError;
80
81    fn check(value: &T) -> Result<(), Self::Error> {
82        if value.as_ref().ends_with(S::VALUE) {
83            Ok(())
84        } else {
85            Err(Self::Error::new(S::VALUE))
86        }
87    }
88
89    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(
91            formatter,
92            "string ending with `{suffix}`",
93            suffix = S::VALUE
94        )
95    }
96}
97
98/// Represents errors that occur when the string does not contain [`string`].
99///
100/// [`string`]: Self::string
101#[derive(Debug, Error)]
102#[error("expected string to contain `{string}`")]
103pub struct ContainsError {
104    /// The expected string.
105    pub string: StaticStr,
106}
107
108impl ContainsError {
109    /// Constructs [`Self`].
110    pub const fn new(string: StaticStr) -> Self {
111        Self { string }
112    }
113}
114
115/// Checks if the string contains the specified string `S`.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
117pub struct Contains<S: TypeStr + ?Sized> {
118    string: PhantomData<S>,
119}
120
121impl<T: AsRef<str> + ?Sized, S: TypeStr + ?Sized> Predicate<T> for Contains<S> {
122    type Error = ContainsError;
123
124    fn check(value: &T) -> Result<(), Self::Error> {
125        if value.as_ref().contains(S::VALUE) {
126            Ok(())
127        } else {
128            Err(Self::Error::new(S::VALUE))
129        }
130    }
131
132    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(formatter, "string containing `{string}`", string = S::VALUE)
134    }
135}
136
137/// Represents errors that occur when the string does not start with [`start`] character.
138///
139/// [`start`]: Self::start
140#[derive(Debug, Error)]
141#[error("expected string to start with `{start}`")]
142pub struct StartsWithCharError {
143    /// The expected starting character.
144    pub start: char,
145}
146
147impl StartsWithCharError {
148    /// Constructs [`Self`].
149    pub const fn new(start: char) -> Self {
150        Self { start }
151    }
152}
153
154/// Checks if the string starts with the specified character `C`.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
156pub struct StartsWithChar<const C: char>;
157
158impl<T: AsRef<str> + ?Sized, const C: char> Predicate<T> for StartsWithChar<C> {
159    type Error = StartsWithCharError;
160
161    fn check(value: &T) -> Result<(), Self::Error> {
162        if value.as_ref().starts_with(C) {
163            Ok(())
164        } else {
165            Err(Self::Error::new(C))
166        }
167    }
168
169    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(formatter, "string starting with `{C}`")
171    }
172}
173
174/// Represents errors that occur when the string does not end with [`end`] character.
175///
176/// [`end`]: Self::end
177#[derive(Debug, Error)]
178#[error("expected string to end with `{end}`")]
179pub struct EndsWithCharError {
180    /// The expected ending character.
181    pub end: char,
182}
183
184impl EndsWithCharError {
185    /// Constructs [`Self`].
186    pub const fn new(end: char) -> Self {
187        Self { end }
188    }
189}
190
191/// Checks if the string ends with the specified character `C`.
192#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
193pub struct EndsWithChar<const C: char>;
194
195impl<T: AsRef<str> + ?Sized, const C: char> Predicate<T> for EndsWithChar<C> {
196    type Error = EndsWithCharError;
197
198    fn check(value: &T) -> Result<(), Self::Error> {
199        if value.as_ref().ends_with(C) {
200            Ok(())
201        } else {
202            Err(Self::Error::new(C))
203        }
204    }
205
206    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
207        write!(formatter, "string ending with `{C}`")
208    }
209}
210
211/// Represents errors that occur when the string does not contain [`character`].
212///
213/// [`character`]: Self::character
214#[derive(Debug, Error)]
215#[error("expected string to contain `{character}`")]
216pub struct ContainsCharError {
217    /// The expected character.
218    pub character: char,
219}
220
221impl ContainsCharError {
222    /// Constructs [`Self`].
223    pub const fn new(character: char) -> Self {
224        Self { character }
225    }
226}
227
228/// Checks if the string contains the specified character `C`.
229#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
230pub struct ContainsChar<const C: char>;
231
232impl<T: AsRef<str> + ?Sized, const C: char> Predicate<T> for ContainsChar<C> {
233    type Error = ContainsCharError;
234
235    fn check(value: &T) -> Result<(), Self::Error> {
236        if value.as_ref().contains(C) {
237            Ok(())
238        } else {
239            Err(Self::Error::new(C))
240        }
241    }
242
243    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
244        write!(formatter, "string containing `{C}`")
245    }
246}
247
248/// Represents errors that occur when the string is not trimmed at the start.
249#[derive(Debug, Error, Default)]
250#[error("expected string to be trimmed at the start")]
251pub struct IsTrimmedStartError;
252
253impl IsTrimmedStartError {
254    /// Constructs [`Self`].
255    pub const fn new() -> Self {
256        Self
257    }
258}
259
260/// Checks if the string is trimmed at the start.
261#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
262pub struct IsTrimmedStart;
263
264impl<T: AsRef<str> + ?Sized> Predicate<T> for IsTrimmedStart {
265    type Error = IsTrimmedStartError;
266
267    fn check(value: &T) -> Result<(), Self::Error> {
268        let string = value.as_ref();
269
270        if string.trim() == string {
271            Ok(())
272        } else {
273            Err(Self::Error::new())
274        }
275    }
276
277    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
278        formatter.write_str("string trimmed at the start")
279    }
280}
281
282/// Represents errors that occur when the string is not trimmed at the end.
283#[derive(Debug, Error, Default)]
284#[error("expected string to be trimmed at the end")]
285pub struct IsTrimmedEndError;
286
287impl IsTrimmedEndError {
288    /// Constructs [`Self`].
289    pub const fn new() -> Self {
290        Self
291    }
292}
293
294/// Checks if the string is trimmed at the end.
295#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
296pub struct IsTrimmedEnd;
297
298impl<T: AsRef<str> + ?Sized> Predicate<T> for IsTrimmedEnd {
299    type Error = IsTrimmedEndError;
300
301    fn check(value: &T) -> Result<(), Self::Error> {
302        let string = value.as_ref();
303
304        if string.trim() == string {
305            Ok(())
306        } else {
307            Err(Self::Error::new())
308        }
309    }
310
311    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
312        formatter.write_str("string trimmed at the end")
313    }
314}
315
316/// Represents errors that occur when the string is not trimmed.
317#[derive(Debug, Error, Default)]
318#[error("expected string to be trimmed")]
319pub struct IsTrimmedError;
320
321impl IsTrimmedError {
322    /// Constructs [`Self`].
323    pub const fn new() -> Self {
324        Self
325    }
326}
327
328/// Checks if the string is trimmed.
329#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
330pub struct IsTrimmed;
331
332impl<T: AsRef<str> + ?Sized> Predicate<T> for IsTrimmed {
333    type Error = IsTrimmedError;
334
335    fn check(value: &T) -> Result<(), Self::Error> {
336        let string = value.as_ref();
337
338        if string.trim() == string {
339            Ok(())
340        } else {
341            Err(Self::Error::new())
342        }
343    }
344
345    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
346        formatter.write_str("trimmed string")
347    }
348}
349
350/// Represents errors that occur when the string is not valid ASCII.
351#[derive(Debug, Error, Default)]
352#[error("expected string to be ascii")]
353pub struct IsAsciiError;
354
355impl IsAsciiError {
356    /// Constructs [`Self`].
357    pub const fn new() -> Self {
358        Self
359    }
360}
361
362/// Checks if the string is valid ASCII.
363#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
364pub struct IsAscii;
365
366impl<T: AsRef<str> + ?Sized> Predicate<T> for IsAscii {
367    type Error = IsAsciiError;
368
369    fn check(value: &T) -> Result<(), Self::Error> {
370        if value.as_ref().is_ascii() {
371            Ok(())
372        } else {
373            Err(Self::Error::new())
374        }
375    }
376
377    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
378        formatter.write_str("ascii string")
379    }
380}
381
382/// Represents errors that occur when the string does not match the expected [`pattern`].
383///
384/// [`pattern`]: Self::pattern
385#[cfg(feature = "regex")]
386#[derive(Debug, Error)]
387#[error("received string that does not match the `{pattern}` pattern")]
388pub struct MismatchError {
389    /// The expected pattern.
390    pub pattern: StaticStr,
391}
392
393#[cfg(feature = "regex")]
394impl MismatchError {
395    /// Constructs [`Self`].
396    pub const fn new(pattern: StaticStr) -> Self {
397        Self { pattern }
398    }
399}
400
401/// Checks if the string matches the specified pattern `S`.
402#[cfg(feature = "regex")]
403#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
404pub struct Matches<S: TypeRegex + ?Sized> {
405    pattern: PhantomData<S>,
406}
407
408#[cfg(feature = "regex")]
409impl<T: AsRef<str> + ?Sized, S: TypeRegex + ?Sized> Predicate<T> for Matches<S> {
410    type Error = MismatchError;
411
412    fn check(value: &T) -> Result<(), Self::Error> {
413        let regex = S::get();
414
415        if regex.is_match(value.as_ref()) {
416            Ok(())
417        } else {
418            Err(Self::Error::new(regex.as_str()))
419        }
420    }
421
422    fn expect(formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
423        write!(
424            formatter,
425            "string matching the `{pattern}` pattern",
426            pattern = S::get().as_str()
427        )
428    }
429}