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