1use super::Version;
4use rez_next_common::RezCoreError;
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9enum Bound {
10 Ge(Version),
12 Gt(Version),
14 Le(Version),
16 Lt(Version),
18 Eq(Version),
20 Ne(Version),
22 Compatible(Version),
24 Any,
26 None,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
32struct BoundSet {
33 bounds: Vec<Bound>,
34}
35
36impl BoundSet {
37 fn any() -> Self {
38 BoundSet {
39 bounds: vec![Bound::Any],
40 }
41 }
42
43 fn none() -> Self {
44 BoundSet {
45 bounds: vec![Bound::None],
46 }
47 }
48
49 fn contains(&self, version: &Version) -> bool {
50 for bound in &self.bounds {
51 if !bound_matches(bound, version) {
52 return false;
53 }
54 }
55 true
56 }
57}
58
59fn bound_matches(bound: &Bound, version: &Version) -> bool {
60 match bound {
61 Bound::Any => true,
62 Bound::None => false,
63 Bound::Ge(v) => version >= v,
64 Bound::Gt(v) => version > v,
65 Bound::Le(v) => version <= v,
66 Bound::Lt(v) => version < v,
67 Bound::Eq(v) => version == v,
68 Bound::Ne(v) => version != v,
69 Bound::Compatible(v) => {
70 if version < v {
73 return false;
74 }
75 let parts = v.as_str().split('.').collect::<Vec<_>>();
77 if parts.len() < 2 {
78 return true;
79 }
80 let prefix = &parts[..parts.len() - 1].join(".");
81 version.as_str().starts_with(&format!("{}.", prefix))
83 || version.as_str() == prefix.as_str()
84 }
85 }
86}
87
88#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
90pub struct VersionRange {
91 pub range_str: String,
93 #[serde(skip)]
95 bound_sets: Vec<BoundSet>,
96 #[serde(skip)]
98 is_parsed: bool,
99 #[serde(skip)]
101 subtract_from: Vec<VersionRange>,
102}
103
104impl VersionRange {
105 pub fn new(range_str: String) -> Result<Self, RezCoreError> {
107 Self::parse(&range_str)
108 }
109
110 pub fn any() -> Self {
112 VersionRange {
113 range_str: String::new(),
114 bound_sets: vec![BoundSet::any()],
115 is_parsed: true,
116 subtract_from: Vec::new(),
117 }
118 }
119
120 pub fn none() -> Self {
122 VersionRange {
123 range_str: "!*".to_string(),
124 bound_sets: vec![BoundSet::none()],
125 is_parsed: true,
126 subtract_from: Vec::new(),
127 }
128 }
129
130 pub fn parse(range_str: &str) -> Result<Self, RezCoreError> {
142 let trimmed = range_str.trim();
143 let bound_sets = parse_range_str(trimmed)?;
144 Ok(VersionRange {
145 range_str: range_str.to_string(),
146 bound_sets,
147 is_parsed: true,
148 subtract_from: Vec::new(),
149 })
150 }
151
152 pub fn contains(&self, version: &Version) -> bool {
154 if !self.is_parsed || self.bound_sets.is_empty() {
155 return true;
156 }
157 let in_self = self.bound_sets.iter().any(|bs| bs.contains(version));
159 if !in_self {
160 return false;
161 }
162 for sub_range in &self.subtract_from {
164 if sub_range.contains(version) {
165 return false;
166 }
167 }
168 true
169 }
170
171 pub fn as_str(&self) -> &str {
173 &self.range_str
174 }
175
176 pub fn intersects(&self, other: &VersionRange) -> bool {
178 if self.is_any() || other.is_any() {
180 return true;
181 }
182 for bs_self in &self.bound_sets {
185 for bs_other in &other.bound_sets {
186 if bound_sets_intersect(bs_self, bs_other) {
188 return true;
189 }
190 }
191 }
192 false
193 }
194
195 pub fn intersect(&self, other: &VersionRange) -> Option<VersionRange> {
197 if self.is_any() {
198 return Some(other.clone());
199 }
200 if other.is_any() {
201 return Some(self.clone());
202 }
203 let mut result_sets = Vec::new();
206 for bs_self in &self.bound_sets {
207 for bs_other in &other.bound_sets {
208 let mut merged = bs_self.bounds.clone();
209 merged.extend(bs_other.bounds.clone());
210 let merged_set = BoundSet { bounds: merged };
211 if is_bound_set_satisfiable(&merged_set) {
213 result_sets.push(merged_set);
214 }
215 }
216 }
217
218 if result_sets.is_empty() {
219 return None;
220 }
221 let new_str = format!("({})&({})", self.range_str, other.range_str);
222 let mut combined_subtracts = self.subtract_from.clone();
223 combined_subtracts.extend(other.subtract_from.clone());
224 Some(VersionRange {
225 range_str: new_str,
226 bound_sets: result_sets,
227 is_parsed: true,
228 subtract_from: combined_subtracts,
229 })
230 }
231
232 pub fn union(&self, other: &VersionRange) -> VersionRange {
234 let new_str = format!("{}|{}", self.range_str, other.range_str);
235 let mut sets = self.bound_sets.clone();
236 sets.extend(other.bound_sets.clone());
237 VersionRange {
238 range_str: new_str,
239 bound_sets: sets,
240 is_parsed: true,
241 subtract_from: Vec::new(),
242 }
243 }
244
245 pub fn subtract(&self, other: &VersionRange) -> Option<VersionRange> {
248 if other.is_any() {
249 return None; }
251 if other.is_empty() {
252 return Some(self.clone());
253 }
254 if self.is_empty() {
255 return None;
256 }
257 let new_str = format!("({})-({})", self.range_str, other.range_str);
259 let mut subtracts = self.subtract_from.clone();
260 subtracts.push(other.clone());
261 let range = VersionRange {
262 range_str: new_str,
263 bound_sets: self.bound_sets.clone(),
264 is_parsed: true,
265 subtract_from: subtracts,
266 };
267 let probes = self.collect_probe_versions_with_other(other);
269 let has_any = probes.iter().any(|v| range.contains(v));
270 if has_any {
271 Some(range)
272 } else {
273 None
274 }
275 }
276
277 pub fn is_any(&self) -> bool {
279 let s = self.range_str.trim();
280 if s.is_empty() || s == "*" {
281 return true;
282 }
283 self.bound_sets
285 .iter()
286 .all(|bs| bs.bounds.is_empty() || bs.bounds.iter().all(|b| matches!(b, Bound::Any)))
287 }
288
289 pub fn is_subset_of(&self, other: &VersionRange) -> bool {
292 if other.is_any() {
293 return true;
294 }
295 if self.is_any() {
296 return other.is_any();
297 }
298 if self.is_empty() {
299 return true;
300 }
301 let probe_versions = self.collect_probe_versions_with_other(other);
302 for v in &probe_versions {
303 if self.contains(v) && !other.contains(v) {
304 return false;
305 }
306 }
307 true
308 }
309
310 pub fn is_superset_of(&self, other: &VersionRange) -> bool {
312 other.is_subset_of(self)
313 }
314
315 fn collect_probe_versions_with_other(&self, other: &VersionRange) -> Vec<Version> {
317 let mut versions = Vec::new();
318 for range in [self as &VersionRange, other] {
319 for bs in &range.bound_sets {
320 for bound in &bs.bounds {
321 match bound {
322 Bound::Ge(v)
323 | Bound::Gt(v)
324 | Bound::Le(v)
325 | Bound::Lt(v)
326 | Bound::Eq(v)
327 | Bound::Ne(v)
328 | Bound::Compatible(v) => {
329 versions.push(v.clone());
330 if let Ok(bumped) = Version::parse(&format!("{}.999999", v.as_str())) {
331 versions.push(bumped);
332 }
333 }
334 _ => {}
335 }
336 }
337 }
338 }
339 for s in &["0.0.1", "999.999.999"] {
340 if let Ok(v) = Version::parse(s) {
341 versions.push(v);
342 }
343 }
344 versions
345 }
346
347 pub fn is_empty(&self) -> bool {
349 let s = self.range_str.trim();
350 if s == "empty" || s == "!*" {
351 return true;
352 }
353 if self.is_parsed && !self.bound_sets.is_empty() {
354 return self
355 .bound_sets
356 .iter()
357 .all(|bs| bs.bounds.iter().any(|b| matches!(b, Bound::None)));
358 }
359 false
360 }
361}
362
363fn parse_range_str(s: &str) -> Result<Vec<BoundSet>, RezCoreError> {
365 if s.is_empty() || s == "*" {
366 return Ok(vec![BoundSet::any()]);
367 }
368 if s == "empty" || s == "!*" {
369 return Ok(vec![BoundSet::none()]);
370 }
371
372 if s.contains("..") && !s.starts_with('.') {
375 if let Some(dot_pos) = s.find("..") {
377 let left = &s[..dot_pos];
378 let right = &s[dot_pos + 2..];
379 if !left.is_empty()
381 && !right.is_empty()
382 && !left.starts_with('>')
383 && !left.starts_with('<')
384 && !left.starts_with('=')
385 && !left.starts_with('!')
386 && !left.starts_with('~')
387 {
388 let new_s = format!(">={},<{}", left.trim(), right.trim());
390 return parse_range_str(&new_s);
391 }
392 }
393 }
394
395 let or_parts: Vec<&str> = s.split('|').collect();
397 let mut result = Vec::new();
398
399 for or_part in or_parts {
400 let bound_set = parse_conjunction(or_part.trim())?;
401 result.push(bound_set);
402 }
403
404 Ok(result)
405}
406
407fn parse_conjunction(s: &str) -> Result<BoundSet, RezCoreError> {
410 if s.is_empty() || s == "*" {
411 return Ok(BoundSet::any());
412 }
413
414 let s = if s.contains('+')
418 && !s.starts_with('>')
419 && !s.starts_with('<')
420 && !s.starts_with('=')
421 && !s.starts_with('!')
422 && !s.starts_with('~')
423 {
424 if let Some(plus_pos) = s.find('+') {
426 let prefix = &s[..plus_pos];
427 let suffix = &s[plus_pos + 1..];
428 if suffix.is_empty() {
429 format!(">={}", prefix)
431 } else {
432 format!(">={},{}", prefix, suffix)
434 }
435 } else {
436 s.to_string()
437 }
438 } else {
439 s.to_string()
440 };
441
442 let mut bounds = Vec::new();
443
444 let parts = split_constraint_parts(&s);
446
447 for part in parts {
448 let part = part.trim();
449 if part.is_empty() {
450 continue;
451 }
452 let bound = parse_single_constraint(part)?;
453 bounds.push(bound);
454 }
455
456 if bounds.is_empty() {
457 return Ok(BoundSet::any());
458 }
459
460 Ok(BoundSet { bounds })
461}
462
463fn split_constraint_parts(s: &str) -> Vec<String> {
465 let mut parts = Vec::new();
466 let mut current = String::new();
467
468 for ch in s.chars() {
469 if ch == ',' {
470 if !current.trim().is_empty() {
471 parts.push(current.trim().to_string());
472 }
473 current = String::new();
474 } else {
475 current.push(ch);
476 }
477 }
478 if !current.trim().is_empty() {
479 parts.push(current.trim().to_string());
480 }
481
482 let mut final_parts = Vec::new();
484 for part in parts {
485 let space_parts = split_on_operator_boundaries(&part);
486 final_parts.extend(space_parts);
487 }
488
489 final_parts
490}
491
492fn split_on_operator_boundaries(s: &str) -> Vec<String> {
494 let mut parts = Vec::new();
495 let mut current = String::new();
496
497 let chars: Vec<char> = s.chars().collect();
498 let mut i = 0;
499 while i < chars.len() {
500 let ch = chars[i];
501 if ch == ' ' {
502 let mut j = i + 1;
504 while j < chars.len() && chars[j] == ' ' {
505 j += 1;
506 }
507 if j < chars.len() {
508 let next = chars[j];
509 if next == '>' || next == '<' || next == '=' || next == '!' || next == '~' {
510 if !current.trim().is_empty() {
511 parts.push(current.trim().to_string());
512 }
513 current = String::new();
514 i = j;
515 continue;
516 }
517 }
518 current.push(ch);
519 } else {
520 current.push(ch);
521 }
522 i += 1;
523 }
524 if !current.trim().is_empty() {
525 parts.push(current.trim().to_string());
526 }
527 parts
528}
529
530fn parse_single_constraint(s: &str) -> Result<Bound, RezCoreError> {
532 let s = s.trim();
533
534 if s.is_empty() || s == "*" {
535 return Ok(Bound::Any);
536 }
537
538 if let Some(rest) = s.strip_prefix(">=") {
540 let v = Version::parse(rest.trim()).map_err(|e| {
541 RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
542 })?;
543 return Ok(Bound::Ge(v));
544 }
545 if let Some(rest) = s.strip_prefix("<=") {
546 let v = Version::parse(rest.trim()).map_err(|e| {
547 RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
548 })?;
549 return Ok(Bound::Le(v));
550 }
551 if let Some(rest) = s.strip_prefix("==") {
552 let v = Version::parse(rest.trim()).map_err(|e| {
553 RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
554 })?;
555 return Ok(Bound::Eq(v));
556 }
557 if let Some(rest) = s.strip_prefix("!=") {
558 let v = Version::parse(rest.trim()).map_err(|e| {
559 RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
560 })?;
561 return Ok(Bound::Ne(v));
562 }
563 if let Some(rest) = s.strip_prefix("~=") {
564 let v = Version::parse(rest.trim()).map_err(|e| {
565 RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
566 })?;
567 return Ok(Bound::Compatible(v));
568 }
569
570 if let Some(rest) = s.strip_prefix('>') {
572 let v = Version::parse(rest.trim()).map_err(|e| {
573 RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
574 })?;
575 return Ok(Bound::Gt(v));
576 }
577 if let Some(rest) = s.strip_prefix('<') {
578 let v = Version::parse(rest.trim()).map_err(|e| {
579 RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
580 })?;
581 return Ok(Bound::Lt(v));
582 }
583
584 let v = Version::parse(s).map_err(|e| {
586 RezCoreError::VersionRange(format!("Invalid version constraint '{}': {}", s, e))
587 })?;
588 Ok(Bound::Eq(v))
589}
590
591fn is_bound_set_satisfiable(bs: &BoundSet) -> bool {
593 if bs.bounds.iter().any(|b| matches!(b, Bound::None)) {
595 return false;
596 }
597 let mut lower: Option<(&Version, bool)> = None; let mut upper: Option<(&Version, bool)> = None; for bound in &bs.bounds {
602 match bound {
603 Bound::Any => {}
604 Bound::None => return false,
605 Bound::Ge(v) => match lower {
606 None => lower = Some((v, true)),
607 Some((lv, linc)) => {
608 if v > lv || (v == lv && !linc) {
609 lower = Some((v, true));
610 }
611 }
612 },
613 Bound::Gt(v) => match lower {
614 None => lower = Some((v, false)),
615 Some((lv, _)) => {
616 if v >= lv {
617 lower = Some((v, false));
618 }
619 }
620 },
621 Bound::Le(v) => match upper {
622 None => upper = Some((v, true)),
623 Some((uv, uinc)) => {
624 if v < uv || (v == uv && !uinc) {
625 upper = Some((v, true));
626 }
627 }
628 },
629 Bound::Lt(v) => match upper {
630 None => upper = Some((v, false)),
631 Some((uv, _)) => {
632 if v <= uv {
633 upper = Some((v, false));
634 }
635 }
636 },
637 Bound::Eq(v) => {
638 match lower {
640 None => lower = Some((v, true)),
641 Some((lv, linc)) => {
642 if v > lv || (v == lv && !linc) {
643 lower = Some((v, true));
644 } else if v < lv {
645 return false;
647 }
648 }
649 }
650 match upper {
651 None => upper = Some((v, true)),
652 Some((uv, uinc)) => {
653 if v < uv || (v == uv && !uinc) {
654 upper = Some((v, true));
655 } else if v > uv {
656 return false;
657 }
658 }
659 }
660 }
661 Bound::Ne(_) | Bound::Compatible(_) => {}
662 }
663 }
664
665 if let (Some((lv, linc)), Some((uv, uinc))) = (lower, upper) {
667 match lv.cmp(uv) {
668 std::cmp::Ordering::Greater => return false,
669 std::cmp::Ordering::Equal => {
670 if !linc || !uinc {
671 return false;
672 }
673 }
674 std::cmp::Ordering::Less => {}
675 }
676 }
677
678 true
679}
680
681fn bound_sets_intersect(a: &BoundSet, b: &BoundSet) -> bool {
683 let a_any = a.bounds.iter().all(|bnd| matches!(bnd, Bound::Any));
685 let b_any = b.bounds.iter().all(|bnd| matches!(bnd, Bound::Any));
686 if a_any || b_any {
687 return true;
688 }
689
690 let combined_bounds: Vec<&Bound> = a.bounds.iter().chain(b.bounds.iter()).collect();
692
693 let eq_versions: Vec<&Version> = combined_bounds
695 .iter()
696 .filter_map(|b| if let Bound::Eq(v) = b { Some(v) } else { None })
697 .collect();
698 if eq_versions.len() > 1 {
699 let first = eq_versions[0];
700 if eq_versions.iter().any(|v| *v != first) {
701 return false;
702 }
703 }
704
705 let mut lower: Option<(&Version, bool)> = None; let mut upper: Option<(&Version, bool)> = None; for bound in &combined_bounds {
712 match bound {
713 Bound::Ge(v) => match lower {
714 None => lower = Some((v, true)),
715 Some((lv, linc)) => {
716 if v > lv || (v == lv && !linc) {
717 lower = Some((v, true));
718 }
719 }
720 },
721 Bound::Gt(v) => match lower {
722 None => lower = Some((v, false)),
723 Some((lv, _linc)) => {
724 if v >= lv {
725 lower = Some((v, false));
726 }
727 }
728 },
729 Bound::Le(v) => match upper {
730 None => upper = Some((v, true)),
731 Some((uv, uinc)) => {
732 if v < uv || (v == uv && !uinc) {
733 upper = Some((v, true));
734 }
735 }
736 },
737 Bound::Lt(v) => match upper {
738 None => upper = Some((v, false)),
739 Some((uv, _uinc)) => {
740 if v <= uv {
741 upper = Some((v, false));
742 }
743 }
744 },
745 _ => {}
746 }
747 }
748
749 if let (Some((lv, linc)), Some((uv, uinc))) = (lower, upper) {
751 match lv.cmp(uv) {
752 std::cmp::Ordering::Greater => return false,
753 std::cmp::Ordering::Equal => {
754 if !linc || !uinc {
756 return false;
757 }
758 }
759 std::cmp::Ordering::Less => {} }
761 }
762
763 true
764}