Skip to main content

semver/
impls.rs

1use crate::identifier::Identifier;
2use crate::{BuildMetadata, Comparator, Prerelease, VersionReq};
3use alloc::vec::Vec;
4use core::cmp::Ordering;
5use core::hash::{Hash, Hasher};
6use core::ops::Deref;
7
8impl Default for Identifier {
9    fn default() -> Self {
10        Identifier::empty()
11    }
12}
13
14impl Eq for Identifier {}
15
16impl Hash for Identifier {
17    fn hash<H: Hasher>(&self, hasher: &mut H) {
18        self.as_str().hash(hasher);
19    }
20}
21
22impl Deref for Prerelease {
23    type Target = str;
24
25    fn deref(&self) -> &Self::Target {
26        self.identifier.as_str()
27    }
28}
29
30impl Deref for BuildMetadata {
31    type Target = str;
32
33    fn deref(&self) -> &Self::Target {
34        self.identifier.as_str()
35    }
36}
37
38impl PartialOrd for Prerelease {
39    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
40        Some(self.cmp(rhs))
41    }
42}
43
44impl PartialOrd for BuildMetadata {
45    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
46        Some(self.cmp(rhs))
47    }
48}
49
50impl Ord for Prerelease {
51    fn cmp(&self, rhs: &Self) -> Ordering {
52        if self.identifier.ptr_eq(&rhs.identifier) {
53            return Ordering::Equal;
54        }
55
56        match self.is_empty() {
57            // A real release compares greater than prerelease.
58            true => return Ordering::Greater,
59            // Prerelease compares less than the real release.
60            false if rhs.is_empty() => return Ordering::Less,
61            false => {}
62        }
63
64        let lhs = self.as_str().split('.');
65        let mut rhs = rhs.as_str().split('.');
66
67        for lhs in lhs {
68            let Some(rhs) = rhs.next() else {
69                // Spec: "A larger set of pre-release fields has a higher
70                // precedence than a smaller set, if all of the preceding
71                // identifiers are equal."
72                return Ordering::Greater;
73            };
74
75            let string_cmp = || Ord::cmp(lhs, rhs);
76            let is_ascii_digit = |b: u8| b.is_ascii_digit();
77            let ordering = match (
78                lhs.bytes().all(is_ascii_digit),
79                rhs.bytes().all(is_ascii_digit),
80            ) {
81                // Respect numeric ordering, for example 99 < 100. Spec says:
82                // "Identifiers consisting of only digits are compared
83                // numerically."
84                (true, true) => Ord::cmp(&lhs.len(), &rhs.len()).then_with(string_cmp),
85                // Spec: "Numeric identifiers always have lower precedence than
86                // non-numeric identifiers."
87                (true, false) => return Ordering::Less,
88                (false, true) => return Ordering::Greater,
89                // Spec: "Identifiers with letters or hyphens are compared
90                // lexically in ASCII sort order."
91                (false, false) => string_cmp(),
92            };
93
94            if ordering != Ordering::Equal {
95                return ordering;
96            }
97        }
98
99        if rhs.next().is_none() {
100            Ordering::Equal
101        } else {
102            Ordering::Less
103        }
104    }
105}
106
107impl Ord for BuildMetadata {
108    fn cmp(&self, rhs: &Self) -> Ordering {
109        if self.identifier.ptr_eq(&rhs.identifier) {
110            return Ordering::Equal;
111        }
112
113        let lhs = self.as_str().split('.');
114        let mut rhs = rhs.as_str().split('.');
115
116        for lhs in lhs {
117            let Some(rhs) = rhs.next() else {
118                return Ordering::Greater;
119            };
120
121            let is_ascii_digit = |b: u8| b.is_ascii_digit();
122            let ordering = match (
123                lhs.bytes().all(is_ascii_digit),
124                rhs.bytes().all(is_ascii_digit),
125            ) {
126                (true, true) => {
127                    // 0 < 00 < 1 < 01 < 001 < 2 < 02 < 002 < 10
128                    let lhval = lhs.trim_start_matches('0');
129                    let rhval = rhs.trim_start_matches('0');
130                    Ord::cmp(&lhval.len(), &rhval.len())
131                        .then_with(|| Ord::cmp(lhval, rhval))
132                        .then_with(|| Ord::cmp(&lhs.len(), &rhs.len()))
133                }
134                (true, false) => return Ordering::Less,
135                (false, true) => return Ordering::Greater,
136                (false, false) => Ord::cmp(lhs, rhs),
137            };
138
139            if ordering != Ordering::Equal {
140                return ordering;
141            }
142        }
143
144        if rhs.next().is_none() {
145            Ordering::Equal
146        } else {
147            Ordering::Less
148        }
149    }
150}
151
152impl FromIterator<Comparator> for VersionReq {
153    fn from_iter<I>(iter: I) -> Self
154    where
155        I: IntoIterator<Item = Comparator>,
156    {
157        let comparators = Vec::from_iter(iter);
158        VersionReq { comparators }
159    }
160}