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, MatcherResult},
18 };
19 use std::{fmt::Debug, marker::PhantomData};
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<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT>( inner: InnerMatcherT, ) -> impl Matcher<ActualT = ActualT> where InnerMatcherT: Matcher<ActualT = String>,51 pub fn is_utf8_string<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT>(
52 inner: InnerMatcherT,
53 ) -> impl Matcher<ActualT = ActualT>
54 where
55 InnerMatcherT: Matcher<ActualT = String>,
56 {
57 IsEncodedStringMatcher { inner, phantom: Default::default() }
58 }
59
60 struct IsEncodedStringMatcher<ActualT, InnerMatcherT> {
61 inner: InnerMatcherT,
62 phantom: PhantomData<ActualT>,
63 }
64
65 impl<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT> Matcher
66 for IsEncodedStringMatcher<ActualT, InnerMatcherT>
67 where
68 InnerMatcherT: Matcher<ActualT = String>,
69 {
70 type ActualT = ActualT;
71
matches(&self, actual: &Self::ActualT) -> MatcherResult72 fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
73 String::from_utf8(actual.as_ref().to_vec())
74 .map(|s| self.inner.matches(&s))
75 .unwrap_or(MatcherResult::NoMatch)
76 }
77
describe(&self, matcher_result: MatcherResult) -> Description78 fn describe(&self, matcher_result: MatcherResult) -> Description {
79 match matcher_result {
80 MatcherResult::Match => format!(
81 "is a UTF-8 encoded string which {}",
82 self.inner.describe(MatcherResult::Match)
83 )
84 .into(),
85 MatcherResult::NoMatch => format!(
86 "is not a UTF-8 encoded string which {}",
87 self.inner.describe(MatcherResult::Match)
88 )
89 .into(),
90 }
91 }
92
explain_match(&self, actual: &Self::ActualT) -> Description93 fn explain_match(&self, actual: &Self::ActualT) -> Description {
94 match String::from_utf8(actual.as_ref().to_vec()) {
95 Ok(s) => {
96 format!("which is a UTF-8 encoded string {}", self.inner.explain_match(&s)).into()
97 }
98 Err(e) => format!("which is not a UTF-8 encoded string: {e}").into(),
99 }
100 }
101 }
102
103 #[cfg(test)]
104 mod tests {
105 use crate::matcher::MatcherResult;
106 use crate::prelude::*;
107
108 #[test]
matches_string_as_byte_slice() -> Result<()>109 fn matches_string_as_byte_slice() -> Result<()> {
110 verify_that!("A string".as_bytes(), is_utf8_string(eq("A string")))
111 }
112
113 #[test]
matches_string_as_byte_vec() -> Result<()>114 fn matches_string_as_byte_vec() -> Result<()> {
115 verify_that!("A string".as_bytes().to_vec(), is_utf8_string(eq("A string")))
116 }
117
118 #[test]
matches_string_with_utf_8_encoded_sequences() -> Result<()>119 fn matches_string_with_utf_8_encoded_sequences() -> Result<()> {
120 verify_that!("äöüÄÖÜ".as_bytes().to_vec(), is_utf8_string(eq("äöüÄÖÜ")))
121 }
122
123 #[test]
does_not_match_non_equal_string() -> Result<()>124 fn does_not_match_non_equal_string() -> Result<()> {
125 verify_that!("äöüÄÖÜ".as_bytes().to_vec(), not(is_utf8_string(eq("A string"))))
126 }
127
128 #[test]
does_not_match_non_utf_8_encoded_byte_sequence() -> Result<()>129 fn does_not_match_non_utf_8_encoded_byte_sequence() -> Result<()> {
130 verify_that!(&[192, 64, 255, 32], not(is_utf8_string(eq("A string"))))
131 }
132
133 #[test]
has_correct_description_in_matched_case() -> Result<()>134 fn has_correct_description_in_matched_case() -> Result<()> {
135 let matcher = is_utf8_string::<&[u8], _>(eq("A string"));
136
137 verify_that!(
138 matcher.describe(MatcherResult::Match),
139 displays_as(eq("is a UTF-8 encoded string which is equal to \"A string\""))
140 )
141 }
142
143 #[test]
has_correct_description_in_not_matched_case() -> Result<()>144 fn has_correct_description_in_not_matched_case() -> Result<()> {
145 let matcher = is_utf8_string::<&[u8], _>(eq("A string"));
146
147 verify_that!(
148 matcher.describe(MatcherResult::NoMatch),
149 displays_as(eq("is not a UTF-8 encoded string which is equal to \"A string\""))
150 )
151 }
152
153 #[test]
has_correct_explanation_in_matched_case() -> Result<()>154 fn has_correct_explanation_in_matched_case() -> Result<()> {
155 let explanation = is_utf8_string(eq("A string")).explain_match(&"A string".as_bytes());
156
157 verify_that!(
158 explanation,
159 displays_as(eq("which is a UTF-8 encoded string which is equal to \"A string\""))
160 )
161 }
162
163 #[test]
has_correct_explanation_when_byte_array_is_not_utf8_encoded() -> Result<()>164 fn has_correct_explanation_when_byte_array_is_not_utf8_encoded() -> Result<()> {
165 let explanation = is_utf8_string(eq("A string")).explain_match(&&[192, 128, 0, 64]);
166
167 verify_that!(explanation, displays_as(starts_with("which is not a UTF-8 encoded string: ")))
168 }
169
170 #[test]
has_correct_explanation_when_inner_matcher_does_not_match() -> Result<()>171 fn has_correct_explanation_when_inner_matcher_does_not_match() -> Result<()> {
172 let explanation =
173 is_utf8_string(eq("A string")).explain_match(&"Another string".as_bytes());
174
175 verify_that!(
176 explanation,
177 displays_as(eq("which is a UTF-8 encoded string which isn't equal to \"A string\""))
178 )
179 }
180 }
181