semver_php/constraint/
single.rs1use super::{Bound, Constraint, Operator};
2use crate::version::version_compare;
3use std::{cmp::Ordering, fmt};
4
5#[derive(Debug, Clone)]
7pub struct SingleConstraint {
8 operator: Operator,
9 version: String,
10 pretty_string: Option<String>,
11}
12
13impl SingleConstraint {
14 pub fn new(operator: Operator, version: impl Into<String>) -> Self {
16 Self {
17 operator,
18 version: version.into(),
19 pretty_string: None,
20 }
21 }
22
23 #[must_use]
25 pub const fn operator(&self) -> Operator {
26 self.operator
27 }
28
29 #[must_use]
31 pub fn version(&self) -> &str {
32 &self.version
33 }
34
35 #[must_use]
37 pub fn is_dev_branch(&self) -> bool {
38 self.version.starts_with("dev-")
39 }
40
41 #[must_use]
44 pub fn match_specific(&self, other: &Self, compare_branches: bool) -> bool {
45 let self_is_branch = self.is_dev_branch();
46 let other_is_branch = other.is_dev_branch();
47
48 let is_ne = self.operator == Operator::Ne;
50 let is_other_ne = other.operator == Operator::Ne;
51
52 if is_ne || is_other_ne {
53 if is_ne && !is_other_ne && other.operator != Operator::Eq && other_is_branch {
56 return false;
57 }
58 if is_other_ne && !is_ne && self.operator != Operator::Eq && self_is_branch {
59 return false;
60 }
61
62 if self.operator != Operator::Eq && other.operator != Operator::Eq {
64 return true;
65 }
66
67 if self_is_branch || other_is_branch {
69 return self.version != other.version;
70 }
71 }
73
74 if self_is_branch && other_is_branch {
76 if self.operator == Operator::Eq && other.operator == Operator::Eq {
78 return self.version == other.version;
79 }
80
81 return false;
83 }
84
85 if self_is_branch != other_is_branch {
87 if !compare_branches {
89 return false;
90 }
91
92 }
95
96 if self.operator == Operator::Ne {
100 if other.operator == Operator::Eq {
102 return self.version != other.version;
103 }
104 return true;
105 }
106 if other.operator == Operator::Ne {
107 if self.operator == Operator::Eq {
108 return self.version != other.version;
109 }
110 return true;
111 }
112
113 let cmp = version_compare(&self.version, &other.version);
115
116 match (&self.operator, &other.operator) {
118 (Operator::Eq, Operator::Eq) => cmp == Ordering::Equal,
120
121 (Operator::Eq, op) | (op, Operator::Eq) => {
123 let (eq_version, other_op, other_version) = if self.operator == Operator::Eq {
124 (&self.version, op, &other.version)
125 } else {
126 (&other.version, op, &self.version)
127 };
128
129 let cmp = version_compare(eq_version, other_version);
130 match other_op {
131 Operator::Lt => cmp == Ordering::Less,
132 Operator::Le => cmp != Ordering::Greater,
133 Operator::Gt => cmp == Ordering::Greater,
134 Operator::Ge => cmp != Ordering::Less,
135 Operator::Eq => cmp == Ordering::Equal,
136 Operator::Ne => cmp != Ordering::Equal,
137 }
138 },
139
140 (Operator::Lt | Operator::Le, Operator::Lt | Operator::Le)
143 | (Operator::Gt | Operator::Ge, Operator::Gt | Operator::Ge) => true,
144
145 (Operator::Lt | Operator::Le, Operator::Gt) | (Operator::Lt, Operator::Ge) => {
147 cmp == Ordering::Greater
148 },
149 (Operator::Le, Operator::Ge) => cmp != Ordering::Less,
150
151 (Operator::Gt | Operator::Ge, Operator::Lt) | (Operator::Gt, Operator::Le) => {
152 cmp == Ordering::Less
153 },
154 (Operator::Ge, Operator::Le) => cmp != Ordering::Greater,
155
156 _ => false,
157 }
158 }
159
160 fn extract_lower_bound(&self) -> Bound {
162 match self.operator {
163 Operator::Ne | Operator::Lt | Operator::Le => Bound::zero(),
164 Operator::Gt => Bound::new(&self.version, false),
165 Operator::Eq | Operator::Ge => Bound::new(&self.version, true),
166 }
167 }
168
169 fn extract_upper_bound(&self) -> Bound {
171 match self.operator {
172 Operator::Ne | Operator::Gt | Operator::Ge => Bound::positive_infinity(),
173 Operator::Lt => Bound::new(&self.version, false),
174 Operator::Eq | Operator::Le => Bound::new(&self.version, true),
175 }
176 }
177}
178
179impl Constraint for SingleConstraint {
180 fn matches(&self, other: &dyn Constraint) -> bool {
181 if let Some(single) = other.as_single() {
183 return self.match_specific(single, false);
184 }
185
186 if other.is_match_all() {
187 return true;
188 }
189
190 if other.is_match_none() {
191 return false;
192 }
193
194 if let Some(multi) = other.as_multi() {
196 return multi.matches(self);
197 }
198
199 true
202 }
203
204 fn lower_bound(&self) -> Bound {
205 self.extract_lower_bound()
206 }
207
208 fn upper_bound(&self) -> Bound {
209 self.extract_upper_bound()
210 }
211
212 fn set_pretty_string(&mut self, pretty: String) {
213 self.pretty_string = Some(pretty);
214 }
215
216 fn pretty_string(&self) -> String {
217 self.pretty_string
218 .clone()
219 .unwrap_or_else(|| self.to_string())
220 }
221
222 fn as_single(&self) -> Option<&SingleConstraint> {
223 Some(self)
224 }
225}
226
227impl fmt::Display for SingleConstraint {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 let op = if self.operator == Operator::Eq {
231 "=="
232 } else {
233 self.operator.as_str()
234 };
235 write!(f, "{} {}", op, self.version)
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_single_constraint_creation() {
245 let c = SingleConstraint::new(Operator::Ge, "1.0.0");
246 assert_eq!(c.operator(), Operator::Ge);
247 assert_eq!(c.version(), "1.0.0");
248 assert!(!c.is_dev_branch());
249 }
250
251 #[test]
252 fn test_dev_branch_detection() {
253 let c = SingleConstraint::new(Operator::Eq, "dev-master");
254 assert!(c.is_dev_branch());
255
256 let c2 = SingleConstraint::new(Operator::Eq, "1.0.0");
257 assert!(!c2.is_dev_branch());
258 }
259
260 #[test]
261 fn test_basic_matching() {
262 let eq_2 = SingleConstraint::new(Operator::Eq, "2.0.0.0");
263 let lt_3 = SingleConstraint::new(Operator::Lt, "3.0.0.0");
264 assert!(eq_2.match_specific(<_3, false));
265
266 let gt_3 = SingleConstraint::new(Operator::Gt, "3.0.0.0");
267 assert!(!eq_2.match_specific(>_3, false));
268 }
269
270 #[test]
271 fn test_bounds() {
272 let c = SingleConstraint::new(Operator::Ge, "1.0.0");
273 let lower = c.lower_bound();
274 assert_eq!(lower.version(), "1.0.0");
275 assert!(lower.is_inclusive());
276
277 let upper = c.upper_bound();
278 assert!(upper.is_positive_infinity());
279 }
280}