semver_php/constraint/
multi.rs1use super::{Bound, Constraint, MatchAllConstraint, MatchNoneConstraint};
2use std::fmt;
3
4#[derive(Debug)]
6pub struct MultiConstraint {
7 constraints: Vec<Box<dyn Constraint>>,
8 conjunctive: bool, pretty_string: Option<String>,
10}
11
12impl MultiConstraint {
13 #[must_use]
16 pub fn new(constraints: Vec<Box<dyn Constraint>>, conjunctive: bool) -> Self {
17 Self {
18 constraints,
19 conjunctive,
20 pretty_string: None,
21 }
22 }
23
24 #[must_use]
30 pub fn create(constraints: Vec<Box<dyn Constraint>>, conjunctive: bool) -> Box<dyn Constraint> {
31 let mut filtered: Vec<Box<dyn Constraint>> = Vec::new();
32
33 for c in constraints {
34 if conjunctive && c.is_match_all() {
36 continue;
37 }
38 if !conjunctive && c.is_match_none() {
39 continue;
40 }
41
42 if conjunctive && c.is_match_none() {
44 return Box::new(MatchNoneConstraint::new());
45 }
46 if !conjunctive && c.is_match_all() {
48 return Box::new(MatchAllConstraint::new());
49 }
50
51 if let Some(multi) = c.as_multi() {
54 if multi.conjunctive == conjunctive {
55 }
58 }
59
60 filtered.push(c);
61 }
62
63 match filtered.len() {
64 0 => {
65 if conjunctive {
66 Box::new(MatchAllConstraint::new())
67 } else {
68 Box::new(MatchNoneConstraint::new())
69 }
70 },
71 1 => filtered.into_iter().next().unwrap(),
72 _ => Box::new(Self::new(filtered, conjunctive)),
73 }
74 }
75
76 #[must_use]
78 pub const fn is_conjunctive(&self) -> bool {
79 self.conjunctive
80 }
81
82 #[must_use]
84 pub const fn is_disjunctive(&self) -> bool {
85 !self.conjunctive
86 }
87
88 #[must_use]
90 pub fn constraints(&self) -> &[Box<dyn Constraint>] {
91 &self.constraints
92 }
93}
94
95impl Constraint for MultiConstraint {
96 fn matches(&self, other: &dyn Constraint) -> bool {
97 if other.is_match_none() {
98 return false;
99 }
100
101 if other.is_match_all() {
102 return true;
103 }
104
105 if let Some(other_multi) = other.as_multi() {
107 if other_multi.is_disjunctive() {
108 for other_c in &other_multi.constraints {
110 if self.matches(other_c.as_ref()) {
111 return true;
112 }
113 }
114 return false;
115 }
116 }
117
118 if self.conjunctive {
119 self.constraints.iter().all(|c| c.matches(other))
121 } else {
122 self.constraints.iter().any(|c| c.matches(other))
124 }
125 }
126
127 fn lower_bound(&self) -> Bound {
128 if self.constraints.is_empty() {
129 return Bound::zero();
130 }
131
132 if self.conjunctive {
133 self.constraints
135 .iter()
136 .map(|c| c.lower_bound())
137 .max_by(Bound::compare_as_lower)
138 .unwrap_or_else(Bound::zero)
139 } else {
140 self.constraints
142 .iter()
143 .map(|c| c.lower_bound())
144 .min_by(Bound::compare_as_lower)
145 .unwrap_or_else(Bound::zero)
146 }
147 }
148
149 fn upper_bound(&self) -> Bound {
150 if self.constraints.is_empty() {
151 return Bound::positive_infinity();
152 }
153
154 if self.conjunctive {
155 self.constraints
157 .iter()
158 .map(|c| c.upper_bound())
159 .min_by(Bound::compare_as_upper)
160 .unwrap_or_else(Bound::positive_infinity)
161 } else {
162 self.constraints
164 .iter()
165 .map(|c| c.upper_bound())
166 .max_by(Bound::compare_as_upper)
167 .unwrap_or_else(Bound::positive_infinity)
168 }
169 }
170
171 fn set_pretty_string(&mut self, pretty: String) {
172 self.pretty_string = Some(pretty);
173 }
174
175 fn pretty_string(&self) -> String {
176 self.pretty_string
177 .clone()
178 .unwrap_or_else(|| self.to_string())
179 }
180
181 fn as_multi(&self) -> Option<&MultiConstraint> {
182 Some(self)
183 }
184}
185
186impl fmt::Display for MultiConstraint {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 let sep = if self.conjunctive { " " } else { " || " };
189 let parts: Vec<String> = self.constraints.iter().map(ToString::to_string).collect();
190 write!(f, "[{}]", parts.join(sep))
191 }
192}
193
194impl Clone for MultiConstraint {
195 fn clone(&self) -> Self {
196 Self {
200 constraints: Vec::new(), conjunctive: self.conjunctive,
202 pretty_string: self.pretty_string.clone(),
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::constraint::{Operator, SingleConstraint};
211
212 #[test]
213 fn test_conjunctive_matching() {
214 let c1 = Box::new(SingleConstraint::new(Operator::Gt, "1.0.0.0"));
215 let c2 = Box::new(SingleConstraint::new(Operator::Lt, "2.0.0.0"));
216 let multi = MultiConstraint::new(vec![c1, c2], true);
217
218 let v = SingleConstraint::new(Operator::Eq, "1.5.0.0");
220 assert!(multi.matches(&v));
221
222 let v2 = SingleConstraint::new(Operator::Eq, "0.5.0.0");
224 assert!(!multi.matches(&v2));
225 }
226
227 #[test]
228 fn test_disjunctive_matching() {
229 let c1 = Box::new(SingleConstraint::new(Operator::Lt, "1.0.0.0"));
230 let c2 = Box::new(SingleConstraint::new(Operator::Gt, "2.0.0.0"));
231 let multi = MultiConstraint::new(vec![c1, c2], false);
232
233 let v = SingleConstraint::new(Operator::Eq, "0.5.0.0");
235 assert!(multi.matches(&v));
236
237 let v2 = SingleConstraint::new(Operator::Eq, "3.0.0.0");
239 assert!(multi.matches(&v2));
240
241 let v3 = SingleConstraint::new(Operator::Eq, "1.5.0.0");
243 assert!(!multi.matches(&v3));
244 }
245
246 #[test]
247 fn test_create_optimization() {
248 let result = MultiConstraint::create(vec![], true);
250 assert!(result.is_match_all());
251
252 let result = MultiConstraint::create(vec![], false);
254 assert!(result.is_match_none());
255
256 let c = Box::new(SingleConstraint::new(Operator::Eq, "1.0.0"));
258 let result = MultiConstraint::create(vec![c], true);
259 assert!(result.as_single().is_some());
260 }
261}