• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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