1use 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#[derive(Debug, Error)]
16#[error("expected string to start with `{prefix}`")]
17pub struct StartsWithError {
18 pub prefix: StaticStr,
20}
21
22impl StartsWithError {
23 pub const fn new(prefix: StaticStr) -> Self {
25 Self { prefix }
26 }
27}
28
29#[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#[derive(Debug, Error)]
59#[error("expected string to end with `{suffix}`")]
60pub struct EndsWithError {
61 pub suffix: StaticStr,
63}
64
65impl EndsWithError {
66 pub const fn new(suffix: StaticStr) -> Self {
68 Self { suffix }
69 }
70}
71
72#[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#[derive(Debug, Error)]
102#[error("expected string to contain `{string}`")]
103pub struct ContainsError {
104 pub string: StaticStr,
106}
107
108impl ContainsError {
109 pub const fn new(string: StaticStr) -> Self {
111 Self { string }
112 }
113}
114
115#[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#[derive(Debug, Error)]
141#[error("expected string to start with `{start}`")]
142pub struct StartsWithCharError {
143 pub start: char,
145}
146
147impl StartsWithCharError {
148 pub const fn new(start: char) -> Self {
150 Self { start }
151 }
152}
153
154#[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#[derive(Debug, Error)]
178#[error("expected string to end with `{end}`")]
179pub struct EndsWithCharError {
180 pub end: char,
182}
183
184impl EndsWithCharError {
185 pub const fn new(end: char) -> Self {
187 Self { end }
188 }
189}
190
191#[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#[derive(Debug, Error)]
215#[error("expected string to contain `{character}`")]
216pub struct ContainsCharError {
217 pub character: char,
219}
220
221impl ContainsCharError {
222 pub const fn new(character: char) -> Self {
224 Self { character }
225 }
226}
227
228#[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#[derive(Debug, Error, Default)]
250#[error("expected string to be trimmed at the start")]
251pub struct IsTrimmedStartError;
252
253impl IsTrimmedStartError {
254 pub const fn new() -> Self {
256 Self
257 }
258}
259
260#[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#[derive(Debug, Error, Default)]
284#[error("expected string to be trimmed at the end")]
285pub struct IsTrimmedEndError;
286
287impl IsTrimmedEndError {
288 pub const fn new() -> Self {
290 Self
291 }
292}
293
294#[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#[derive(Debug, Error, Default)]
318#[error("expected string to be trimmed")]
319pub struct IsTrimmedError;
320
321impl IsTrimmedError {
322 pub const fn new() -> Self {
324 Self
325 }
326}
327
328#[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#[derive(Debug, Error, Default)]
352#[error("expected string to be ascii")]
353pub struct IsAsciiError;
354
355impl IsAsciiError {
356 pub const fn new() -> Self {
358 Self
359 }
360}
361
362#[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#[cfg(feature = "regex")]
386#[derive(Debug, Error)]
387#[error("received string that does not match the `{pattern}` pattern")]
388pub struct MismatchError {
389 pub pattern: StaticStr,
391}
392
393#[cfg(feature = "regex")]
394impl MismatchError {
395 pub const fn new(pattern: StaticStr) -> Self {
397 Self { pattern }
398 }
399}
400
401#[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}