1// Copyright (c) 2018 The predicates-rs Project Developers.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/license/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::ffi;
10use std::fmt;
11use std::str;
12
13use crate::reflection;
14#[cfg(feature = "normalize-line-endings")]
15use crate::str::normalize::NormalizedPredicate;
16use crate::Predicate;
17
18/// Predicate adaper that trims the variable being tested.
19///
20/// This is created by `pred.trim()`.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct TrimPredicate<P>
23where
24 P: Predicate<str>,
25{
26 p: P,
27}
28
29impl<P> Predicate<str> for TrimPredicate<P>
30where
31 P: Predicate<str>,
32{
33 fn eval(&self, variable: &str) -> bool {
34 self.p.eval(variable.trim())
35 }
36
37 fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
38 self.p.find_case(expected, variable.trim())
39 }
40}
41
42impl<P> reflection::PredicateReflection for TrimPredicate<P>
43where
44 P: Predicate<str>,
45{
46 fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
47 let params = vec![reflection::Child::new("predicate", &self.p)];
48 Box::new(params.into_iter())
49 }
50}
51
52impl<P> fmt::Display for TrimPredicate<P>
53where
54 P: Predicate<str>,
55{
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 self.p.fmt(f)
58 }
59}
60
61/// Predicate adaper that converts a `str` predicate to byte predicate.
62///
63/// This is created by `pred.from_utf8()`.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct Utf8Predicate<P>
66where
67 P: Predicate<str>,
68{
69 p: P,
70}
71
72impl<P> Predicate<ffi::OsStr> for Utf8Predicate<P>
73where
74 P: Predicate<str>,
75{
76 fn eval(&self, variable: &ffi::OsStr) -> bool {
77 variable.to_str().map(|s| self.p.eval(s)).unwrap_or(false)
78 }
79
80 fn find_case<'a>(
81 &'a self,
82 expected: bool,
83 variable: &ffi::OsStr,
84 ) -> Option<reflection::Case<'a>> {
85 let var_str = variable.to_str();
86 match (expected, var_str) {
87 (_, Some(var_str)) => self.p.find_case(expected, var_str).map(|child| {
88 child.add_product(reflection::Product::new("var as str", var_str.to_owned()))
89 }),
90 (true, None) => None,
91 (false, None) => Some(
92 reflection::Case::new(Some(self), false)
93 .add_product(reflection::Product::new("error", "Invalid UTF-8 string")),
94 ),
95 }
96 }
97}
98
99impl<P> Predicate<[u8]> for Utf8Predicate<P>
100where
101 P: Predicate<str>,
102{
103 fn eval(&self, variable: &[u8]) -> bool {
104 str::from_utf8(variable)
105 .map(|s| self.p.eval(s))
106 .unwrap_or(false)
107 }
108
109 fn find_case<'a>(&'a self, expected: bool, variable: &[u8]) -> Option<reflection::Case<'a>> {
110 let var_str = str::from_utf8(variable);
111 match (expected, var_str) {
112 (_, Ok(var_str)) => self.p.find_case(expected, var_str).map(|child| {
113 child.add_product(reflection::Product::new("var as str", var_str.to_owned()))
114 }),
115 (true, Err(_)) => None,
116 (false, Err(err)) => Some(
117 reflection::Case::new(Some(self), false)
118 .add_product(reflection::Product::new("error", err)),
119 ),
120 }
121 }
122}
123
124impl<P> reflection::PredicateReflection for Utf8Predicate<P>
125where
126 P: Predicate<str>,
127{
128 fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
129 let params = vec![reflection::Child::new("predicate", &self.p)];
130 Box::new(params.into_iter())
131 }
132}
133
134impl<P> fmt::Display for Utf8Predicate<P>
135where
136 P: Predicate<str>,
137{
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 self.p.fmt(f)
140 }
141}
142
143/// `Predicate` extension adapting a `str` Predicate.
144pub trait PredicateStrExt
145where
146 Self: Predicate<str>,
147 Self: Sized,
148{
149 /// Returns a `TrimPredicate` that ensures the data passed to `Self` is trimmed.
150 ///
151 /// # Examples
152 ///
153 /// ```
154 /// use predicates::prelude::*;
155 ///
156 /// let predicate_fn = predicate::str::is_empty().trim();
157 /// assert_eq!(true, predicate_fn.eval(" "));
158 /// assert_eq!(false, predicate_fn.eval(" Hello "));
159 /// ```
160 fn trim(self) -> TrimPredicate<Self> {
161 TrimPredicate { p: self }
162 }
163
164 /// Returns a `Utf8Predicate` that adapts `Self` to a `[u8]` `Predicate`.
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// use predicates::prelude::*;
170 /// use std::ffi::OsStr;
171 ///
172 /// let predicate_fn = predicate::str::is_empty().not().from_utf8();
173 /// assert_eq!(true, predicate_fn.eval(OsStr::new("Hello")));
174 /// assert_eq!(false, predicate_fn.eval(OsStr::new("")));
175 /// let variable: &[u8] = b"";
176 /// assert_eq!(false, predicate_fn.eval(variable));
177 /// ```
178 #[allow(clippy::wrong_self_convention)]
179 fn from_utf8(self) -> Utf8Predicate<Self> {
180 Utf8Predicate { p: self }
181 }
182
183 /// Returns a `NormalizedPredicate` that ensures
184 /// the newlines within the data passed to `Self` is normalised.
185 ///
186 /// # Examples
187 ///
188 /// ```
189 /// use predicates::prelude::*;
190 ///
191 /// let predicate_fn = predicate::eq("Hello World!\n").normalize();
192 /// assert_eq!(true, predicate_fn.eval("Hello World!\n"));
193 /// assert_eq!(true, predicate_fn.eval("Hello World!\r"));
194 /// assert_eq!(true, predicate_fn.eval("Hello World!\r\n"));
195 /// assert_eq!(false, predicate_fn.eval("Goodbye"));
196 /// ```
197 ///
198 #[cfg(feature = "normalize-line-endings")]
199 fn normalize(self) -> NormalizedPredicate<Self> {
200 NormalizedPredicate { p: self }
201 }
202}
203
204impl<P> PredicateStrExt for P where P: Predicate<str> {}
205