1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use crate::{
16 description::Description,
17 matcher::{Matcher, MatcherBase, MatcherResult},
18 };
19 use std::fmt::Debug;
20
21 /// Matches a byte sequence which is a UTF-8 encoded string matched by `inner`.
22 ///
23 /// The matcher reports no match if either the string is not UTF-8 encoded or if
24 /// `inner` does not match on the decoded string.
25 ///
26 /// The input may be a slice `&[u8]` or a `Vec` of bytes.
27 ///
28 /// ```
29 /// # use googletest::prelude::*;
30 /// # fn should_pass() -> Result<()> {
31 /// let bytes: &[u8] = "A string".as_bytes();
32 /// verify_that!(bytes, is_utf8_string(eq("A string")))?; // Passes
33 /// let bytes: Vec<u8> = "A string".as_bytes().to_vec();
34 /// verify_that!(bytes, is_utf8_string(eq("A string")))?; // Passes
35 /// # Ok(())
36 /// # }
37 /// # fn should_fail_1() -> Result<()> {
38 /// # let bytes: &[u8] = "A string".as_bytes();
39 /// verify_that!(bytes, is_utf8_string(eq("Another string")))?; // Fails (inner matcher does not match)
40 /// # Ok(())
41 /// # }
42 /// # fn should_fail_2() -> Result<()> {
43 /// let bytes: Vec<u8> = vec![255, 64, 128, 32];
44 /// verify_that!(bytes, is_utf8_string(anything()))?; // Fails (not UTF-8 encoded)
45 /// # Ok(())
46 /// # }
47 /// # should_pass().unwrap();
48 /// # should_fail_1().unwrap_err();
49 /// # should_fail_2().unwrap_err();
50 /// ```
is_utf8_string<InnerMatcherT>(inner: InnerMatcherT) -> IsEncodedStringMatcher<InnerMatcherT> where InnerMatcherT: for<'a> Matcher<&'a str>,51 pub fn is_utf8_string<InnerMatcherT>(inner: InnerMatcherT) -> IsEncodedStringMatcher<InnerMatcherT>
52 where
53 InnerMatcherT: for<'a> Matcher<&'a str>,
54 {
55 IsEncodedStringMatcher { inner }
56 }
57
58 #[derive(MatcherBase)]
59 pub struct IsEncodedStringMatcher<InnerMatcherT> {
60 inner: InnerMatcherT,
61 }
62
63 impl<ActualT: AsRef<[u8]> + Debug + Copy, InnerMatcherT> Matcher<ActualT>
64 for IsEncodedStringMatcher<InnerMatcherT>
65 where
66 InnerMatcherT: for<'a> Matcher<&'a str>,
67 {
matches(&self, actual: ActualT) -> MatcherResult68 fn matches(&self, actual: ActualT) -> MatcherResult {
69 std::str::from_utf8(actual.as_ref())
70 .map(|s| self.inner.matches(s))
71 .unwrap_or(MatcherResult::NoMatch)
72 }
73
describe(&self, matcher_result: MatcherResult) -> Description74 fn describe(&self, matcher_result: MatcherResult) -> Description {
75 match matcher_result {
76 MatcherResult::Match => format!(
77 "is a UTF-8 encoded string which {}",
78 self.inner.describe(MatcherResult::Match)
79 )
80 .into(),
81 MatcherResult::NoMatch => format!(
82 "is not a UTF-8 encoded string which {}",
83 self.inner.describe(MatcherResult::Match)
84 )
85 .into(),
86 }
87 }
88
explain_match(&self, actual: ActualT) -> Description89 fn explain_match(&self, actual: ActualT) -> Description {
90 match std::str::from_utf8(actual.as_ref()) {
91 Ok(s) => {
92 format!("which is a UTF-8 encoded string {}", self.inner.explain_match(s)).into()
93 }
94 Err(e) => format!("which is not a UTF-8 encoded string: {e}").into(),
95 }
96 }
97 }
98
99 #[cfg(test)]
100 mod tests {
101 use crate::matcher::MatcherResult;
102 use crate::prelude::*;
103
104 #[test]
matches_string_as_byte_slice() -> Result<()>105 fn matches_string_as_byte_slice() -> Result<()> {
106 verify_that!("A string".as_bytes(), is_utf8_string(eq("A string")))
107 }
108
109 #[test]
matches_string_as_byte_vec() -> Result<()>110 fn matches_string_as_byte_vec() -> Result<()> {
111 verify_that!("A string".as_bytes().to_vec(), is_utf8_string(eq("A string")))
112 }
113
114 #[test]
matches_string_with_utf_8_encoded_sequences() -> Result<()>115 fn matches_string_with_utf_8_encoded_sequences() -> Result<()> {
116 verify_that!("äöüÄÖÜ".as_bytes().to_vec(), is_utf8_string(eq("äöüÄÖÜ")))
117 }
118
119 #[test]
does_not_match_non_equal_string() -> Result<()>120 fn does_not_match_non_equal_string() -> Result<()> {
121 verify_that!("äöüÄÖÜ".as_bytes().to_vec(), not(is_utf8_string(eq("A string"))))
122 }
123
124 #[test]
does_not_match_non_utf_8_encoded_byte_sequence() -> Result<()>125 fn does_not_match_non_utf_8_encoded_byte_sequence() -> Result<()> {
126 verify_that!(&[192, 64, 255, 32], not(is_utf8_string(eq("A string"))))
127 }
128
129 #[test]
has_correct_description_in_matched_case() -> Result<()>130 fn has_correct_description_in_matched_case() -> Result<()> {
131 let matcher = is_utf8_string(eq("A string"));
132
133 verify_that!(
134 Matcher::<&[u8]>::describe(&matcher, MatcherResult::Match),
135 displays_as(eq("is a UTF-8 encoded string which is equal to \"A string\""))
136 )
137 }
138
139 #[test]
has_correct_description_in_not_matched_case() -> Result<()>140 fn has_correct_description_in_not_matched_case() -> Result<()> {
141 let matcher = is_utf8_string(eq("A string"));
142
143 verify_that!(
144 Matcher::<&[u8]>::describe(&matcher, MatcherResult::NoMatch),
145 displays_as(eq("is not a UTF-8 encoded string which is equal to \"A string\""))
146 )
147 }
148
149 #[test]
has_correct_explanation_in_matched_case() -> Result<()>150 fn has_correct_explanation_in_matched_case() -> Result<()> {
151 let explanation = is_utf8_string(eq("A string")).explain_match("A string".as_bytes());
152
153 verify_that!(
154 explanation,
155 displays_as(eq("which is a UTF-8 encoded string which is equal to \"A string\""))
156 )
157 }
158
159 #[test]
has_correct_explanation_when_byte_array_is_not_utf8_encoded() -> Result<()>160 fn has_correct_explanation_when_byte_array_is_not_utf8_encoded() -> Result<()> {
161 let explanation = is_utf8_string(eq("A string")).explain_match([192, 128, 0, 64]);
162
163 verify_that!(explanation, displays_as(starts_with("which is not a UTF-8 encoded string: ")))
164 }
165
166 #[test]
has_correct_explanation_when_inner_matcher_does_not_match() -> Result<()>167 fn has_correct_explanation_when_inner_matcher_does_not_match() -> Result<()> {
168 let explanation = is_utf8_string(eq("A string")).explain_match("Another string".as_bytes());
169
170 verify_that!(
171 explanation,
172 displays_as(eq("which is a UTF-8 encoded string which isn't equal to \"A string\""))
173 )
174 }
175 }
176