asserting/string/
mod.rs

1//! Implementation of assertions for string values.
2//!
3//! String assertions are implemented for all string types of Rust:
4//!
5//! * `String` and `str`
6//! * `OsString` and `OsStr`
7//! * `CString` and `CStr`
8
9use crate::assertions::{AssertStringContainsAnyOf, AssertStringPattern};
10use crate::expectations::{StringContains, StringContainsAnyOf, StringEndsWith, StringStartWith};
11use crate::prelude::DefinedOrderProperty;
12use crate::properties::{IsEmptyProperty, LengthProperty};
13use crate::spec::{Expectation, Expression, FailingStrategy, Spec};
14use crate::std::fmt::Debug;
15use crate::std::str::Chars;
16#[cfg(not(feature = "std"))]
17use alloc::{format, string::String};
18
19impl IsEmptyProperty for &str {
20    fn is_empty_property(&self) -> bool {
21        self.is_empty()
22    }
23}
24
25impl LengthProperty for &str {
26    fn length_property(&self) -> usize {
27        self.len()
28    }
29}
30
31impl IsEmptyProperty for String {
32    fn is_empty_property(&self) -> bool {
33        self.is_empty()
34    }
35}
36
37impl LengthProperty for String {
38    fn length_property(&self) -> usize {
39        self.len()
40    }
41}
42
43#[cfg(feature = "std")]
44mod os_string {
45    use crate::properties::{IsEmptyProperty, LengthProperty};
46    use crate::std::ffi::{OsStr, OsString};
47
48    impl IsEmptyProperty for OsString {
49        fn is_empty_property(&self) -> bool {
50            self.is_empty()
51        }
52    }
53
54    impl LengthProperty for OsString {
55        fn length_property(&self) -> usize {
56            self.len()
57        }
58    }
59
60    impl IsEmptyProperty for &OsStr {
61        fn is_empty_property(&self) -> bool {
62            self.is_empty()
63        }
64    }
65
66    impl LengthProperty for &OsStr {
67        fn length_property(&self) -> usize {
68            self.len()
69        }
70    }
71}
72
73#[cfg(feature = "std")]
74mod c_string {
75    use crate::properties::IsEmptyProperty;
76    use crate::std::ffi::{CStr, CString};
77
78    impl IsEmptyProperty for CString {
79        fn is_empty_property(&self) -> bool {
80            self.is_empty()
81        }
82    }
83
84    impl IsEmptyProperty for &CStr {
85        fn is_empty_property(&self) -> bool {
86            self.is_empty()
87        }
88    }
89}
90
91impl DefinedOrderProperty for Chars<'_> {}
92
93// We implement `AssertContains` for different `Pattern` types as the
94// [`core::str::pattern`] API is not stabilized as of February 2025;
95// see issue [#27721](https://github.com/rust-lang/rust/issues/27721).
96// Maybe we keep the implementations for a long time to support an earlier MSRV.
97
98impl<'a, S, R> AssertStringPattern<&'a str> for Spec<'a, S, R>
99where
100    S: 'a + AsRef<str> + Debug,
101    R: FailingStrategy,
102{
103    fn contains(self, pattern: &'a str) -> Self {
104        self.expecting(StringContains { expected: pattern })
105    }
106
107    fn starts_with(self, pattern: &str) -> Self {
108        self.expecting(StringStartWith { expected: pattern })
109    }
110
111    fn ends_with(self, pattern: &str) -> Self {
112        self.expecting(StringEndsWith { expected: pattern })
113    }
114}
115
116impl<'a, S, R> AssertStringPattern<String> for Spec<'a, S, R>
117where
118    S: 'a + AsRef<str> + Debug,
119    R: FailingStrategy,
120{
121    fn contains(self, pattern: String) -> Self {
122        self.expecting(StringContains { expected: pattern })
123    }
124
125    fn starts_with(self, pattern: String) -> Self {
126        self.expecting(StringStartWith { expected: pattern })
127    }
128
129    fn ends_with(self, pattern: String) -> Self {
130        self.expecting(StringEndsWith { expected: pattern })
131    }
132}
133
134impl<'a, S, R> AssertStringPattern<char> for Spec<'a, S, R>
135where
136    S: 'a + AsRef<str> + Debug,
137    R: FailingStrategy,
138{
139    fn contains(self, expected: char) -> Self {
140        self.expecting(StringContains { expected })
141    }
142
143    fn starts_with(self, expected: char) -> Self {
144        self.expecting(StringStartWith { expected })
145    }
146
147    fn ends_with(self, pattern: char) -> Self {
148        self.expecting(StringEndsWith { expected: pattern })
149    }
150}
151
152impl<S> Expectation<S> for StringContains<&str>
153where
154    S: AsRef<str> + Debug,
155{
156    fn test(&mut self, subject: &S) -> bool {
157        subject.as_ref().contains(self.expected)
158    }
159
160    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
161        format!(
162            "expected {expression} to contain {:?}\n   but was: {actual:?}\n  expected: {:?}",
163            self.expected, self.expected
164        )
165    }
166}
167
168impl<S> Expectation<S> for StringContains<String>
169where
170    S: AsRef<str> + Debug,
171{
172    fn test(&mut self, subject: &S) -> bool {
173        subject.as_ref().contains(&self.expected)
174    }
175
176    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
177        format!(
178            "expected {expression} to contain {:?}\n   but was: {actual:?}\n  expected: {:?}",
179            self.expected, self.expected
180        )
181    }
182}
183
184impl<S> Expectation<S> for StringContains<char>
185where
186    S: AsRef<str> + Debug,
187{
188    fn test(&mut self, subject: &S) -> bool {
189        subject.as_ref().contains(self.expected)
190    }
191
192    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
193        format!(
194            "expected {expression} to contain {:?}\n   but was: {actual:?}\n  expected: {:?}",
195            self.expected, self.expected
196        )
197    }
198}
199
200impl<S> Expectation<S> for StringStartWith<&str>
201where
202    S: AsRef<str> + Debug,
203{
204    fn test(&mut self, subject: &S) -> bool {
205        subject.as_ref().starts_with(self.expected)
206    }
207
208    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
209        format!(
210            "expected {expression} to start with {:?}\n   but was: {actual:?}\n  expected: {:?}",
211            self.expected, self.expected
212        )
213    }
214}
215
216impl<S> Expectation<S> for StringStartWith<String>
217where
218    S: AsRef<str> + Debug,
219{
220    fn test(&mut self, subject: &S) -> bool {
221        subject.as_ref().starts_with(&self.expected)
222    }
223
224    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
225        format!(
226            "expected {expression} to start with {:?}\n   but was: {actual:?}\n  expected: {:?}",
227            self.expected, self.expected
228        )
229    }
230}
231
232impl<S> Expectation<S> for StringStartWith<char>
233where
234    S: AsRef<str> + Debug,
235{
236    fn test(&mut self, subject: &S) -> bool {
237        subject.as_ref().starts_with(self.expected)
238    }
239
240    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
241        format!(
242            "expected {expression} to start with {:?}\n   but was: {actual:?}\n  expected: {:?}",
243            self.expected, self.expected
244        )
245    }
246}
247
248impl<S> Expectation<S> for StringEndsWith<&str>
249where
250    S: AsRef<str> + Debug,
251{
252    fn test(&mut self, subject: &S) -> bool {
253        subject.as_ref().ends_with(self.expected)
254    }
255
256    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
257        format!(
258            "expected {expression} to end with {:?}\n   but was: {actual:?}\n  expected: {:?}",
259            self.expected, self.expected
260        )
261    }
262}
263
264impl<S> Expectation<S> for StringEndsWith<String>
265where
266    S: AsRef<str> + Debug,
267{
268    fn test(&mut self, subject: &S) -> bool {
269        subject.as_ref().ends_with(&self.expected)
270    }
271
272    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
273        format!(
274            "expected {expression} to end with {:?}\n   but was: {actual:?}\n  expected: {:?}",
275            self.expected, self.expected
276        )
277    }
278}
279
280impl<S> Expectation<S> for StringEndsWith<char>
281where
282    S: AsRef<str> + Debug,
283{
284    fn test(&mut self, subject: &S) -> bool {
285        subject.as_ref().ends_with(self.expected)
286    }
287
288    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
289        format!(
290            "expected {expression} to end with {:?}\n   but was: {actual:?}\n  expected: {:?}",
291            self.expected, self.expected
292        )
293    }
294}
295
296// When string slices' `contains` function is used with an array of chars or
297// slice of chars it checks if any of the chars in the array/slice is contained
298// in the string slice. Therefore, we implement the [`AssertContainsAnyOf`]
299// assertion for array/slice of chars as expected value, but not the
300// [`AssertContains`] assertion.
301
302impl<'a, S, R> AssertStringContainsAnyOf<&'a [char]> for Spec<'a, S, R>
303where
304    S: 'a + AsRef<str> + Debug,
305    R: FailingStrategy,
306{
307    fn contains_any_of(self, expected: &'a [char]) -> Self {
308        self.expecting(StringContainsAnyOf { expected })
309    }
310}
311
312impl<'a, S, R, const N: usize> AssertStringContainsAnyOf<[char; N]> for Spec<'a, S, R>
313where
314    S: 'a + AsRef<str> + Debug,
315    R: FailingStrategy,
316{
317    fn contains_any_of(self, expected: [char; N]) -> Self {
318        self.expecting(StringContainsAnyOf { expected })
319    }
320}
321
322impl<'a, S, R, const N: usize> AssertStringContainsAnyOf<&'a [char; N]> for Spec<'a, S, R>
323where
324    S: 'a + AsRef<str> + Debug,
325    R: FailingStrategy,
326{
327    fn contains_any_of(self, expected: &'a [char; N]) -> Self {
328        self.expecting(StringContainsAnyOf { expected })
329    }
330}
331
332impl<S> Expectation<S> for StringContainsAnyOf<&[char]>
333where
334    S: AsRef<str> + Debug,
335{
336    fn test(&mut self, subject: &S) -> bool {
337        subject.as_ref().contains(self.expected)
338    }
339
340    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
341        format!(
342            "expected {expression} to contain any of {:?}\n   but was: {actual:?}\n  expected: {:?}",
343            self.expected, self.expected
344        )
345    }
346}
347
348impl<S, const N: usize> Expectation<S> for StringContainsAnyOf<[char; N]>
349where
350    S: AsRef<str> + Debug,
351{
352    fn test(&mut self, subject: &S) -> bool {
353        subject.as_ref().contains(self.expected)
354    }
355
356    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
357        format!(
358            "expected {expression} to contain any of {:?}\n   but was: {actual:?}\n  expected: {:?}",
359            self.expected, self.expected
360        )
361    }
362}
363
364impl<S, const N: usize> Expectation<S> for StringContainsAnyOf<&[char; N]>
365where
366    S: AsRef<str> + Debug,
367{
368    fn test(&mut self, subject: &S) -> bool {
369        subject.as_ref().contains(self.expected)
370    }
371
372    fn message(&self, expression: Expression<'_>, actual: &S) -> String {
373        format!(
374            "expected {expression} to contain any of {:?}\n   but was: {actual:?}\n  expected: {:?}",
375            self.expected, self.expected
376        )
377    }
378}
379
380#[cfg(test)]
381mod tests;