semver_php/constraint/
mod.rs

1pub mod bound;
2pub mod match_all;
3pub mod match_none;
4pub mod multi;
5pub mod single;
6
7pub use bound::Bound;
8pub use match_all::MatchAllConstraint;
9pub use match_none::MatchNoneConstraint;
10pub use multi::MultiConstraint;
11pub use single::SingleConstraint;
12
13use crate::error::{Result, SemverError};
14use std::fmt;
15
16/// Comparison operators for version constraints.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum Operator {
19	/// Equal (== or =)
20	Eq,
21	/// Not equal (!= or <>)
22	Ne,
23	/// Less than (<)
24	Lt,
25	/// Less than or equal (<=)
26	Le,
27	/// Greater than (>)
28	Gt,
29	/// Greater than or equal (>=)
30	Ge,
31}
32
33impl Operator {
34	/// Parse an operator from a string.
35	///
36	/// # Errors
37	/// Returns `SemverError::InvalidOperator` if the string is not a recognized operator.
38	pub fn parse(s: &str) -> Result<Self> {
39		match s {
40			"=" | "==" => Ok(Self::Eq),
41			"!=" | "<>" => Ok(Self::Ne),
42			"<" => Ok(Self::Lt),
43			"<=" => Ok(Self::Le),
44			">" => Ok(Self::Gt),
45			">=" => Ok(Self::Ge),
46			_ => Err(SemverError::InvalidOperator(s.to_string())),
47		}
48	}
49
50	/// Get the string representation of the operator.
51	#[must_use]
52	pub const fn as_str(&self) -> &'static str {
53		match self {
54			Self::Eq => "==",
55			Self::Ne => "!=",
56			Self::Lt => "<",
57			Self::Le => "<=",
58			Self::Gt => ">",
59			Self::Ge => ">=",
60		}
61	}
62
63	/// Get the inverse operator (for matching logic).
64	#[must_use]
65	pub const fn inverse(&self) -> Self {
66		match self {
67			Self::Eq => Self::Eq,
68			Self::Ne => Self::Ne,
69			Self::Lt => Self::Gt,
70			Self::Le => Self::Ge,
71			Self::Gt => Self::Lt,
72			Self::Ge => Self::Le,
73		}
74	}
75}
76
77impl fmt::Display for Operator {
78	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79		write!(f, "{}", self.as_str())
80	}
81}
82
83/// Core trait for all constraint types.
84pub trait Constraint: fmt::Display + fmt::Debug + Send + Sync {
85	/// Check if this constraint matches/intersects with another.
86	fn matches(&self, other: &dyn Constraint) -> bool;
87
88	/// Get the lower bound of this constraint.
89	fn lower_bound(&self) -> Bound;
90
91	/// Get the upper bound of this constraint.
92	fn upper_bound(&self) -> Bound;
93
94	/// Get a human-readable representation.
95	fn pretty_string(&self) -> String {
96		self.to_string()
97	}
98
99	/// Set a custom pretty string (for preserving user input).
100	fn set_pretty_string(&mut self, _pretty: String) {
101		// Default: do nothing
102	}
103
104	/// Check if this is a `MatchAllConstraint`.
105	fn is_match_all(&self) -> bool {
106		false
107	}
108
109	/// Check if this is a `MatchNoneConstraint`.
110	fn is_match_none(&self) -> bool {
111		false
112	}
113
114	/// Try to downcast to `SingleConstraint`.
115	fn as_single(&self) -> Option<&SingleConstraint> {
116		None
117	}
118
119	/// Try to downcast to `MultiConstraint`.
120	fn as_multi(&self) -> Option<&MultiConstraint> {
121		None
122	}
123}
124
125#[cfg(test)]
126mod tests {
127	use super::*;
128
129	#[test]
130	fn test_operator_parse() {
131		assert_eq!(Operator::parse("=").unwrap(), Operator::Eq);
132		assert_eq!(Operator::parse("==").unwrap(), Operator::Eq);
133		assert_eq!(Operator::parse("!=").unwrap(), Operator::Ne);
134		assert_eq!(Operator::parse("<>").unwrap(), Operator::Ne);
135		assert_eq!(Operator::parse("<").unwrap(), Operator::Lt);
136		assert_eq!(Operator::parse("<=").unwrap(), Operator::Le);
137		assert_eq!(Operator::parse(">").unwrap(), Operator::Gt);
138		assert_eq!(Operator::parse(">=").unwrap(), Operator::Ge);
139		assert!(Operator::parse("??").is_err());
140	}
141
142	#[test]
143	fn test_operator_inverse() {
144		assert_eq!(Operator::Lt.inverse(), Operator::Gt);
145		assert_eq!(Operator::Gt.inverse(), Operator::Lt);
146		assert_eq!(Operator::Le.inverse(), Operator::Ge);
147		assert_eq!(Operator::Ge.inverse(), Operator::Le);
148		assert_eq!(Operator::Eq.inverse(), Operator::Eq);
149		assert_eq!(Operator::Ne.inverse(), Operator::Ne);
150	}
151}