1use crate::parsers::modular;
2use crate::{BaseVersion, FullVersionParser, ParserError};
3use std::fmt;
4
5#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
28pub struct FullVersion {
29    pub major: u64,
35    pub minor: u64,
41    pub patch: u64,
43}
44
45impl FullVersion {
46    pub fn new(major: u64, minor: u64, patch: u64) -> Self {
52        Self {
53            major,
54            minor,
55            patch,
56        }
57    }
58
59    pub fn parse(input: &str) -> Result<Self, ParserError> {
63        modular::ModularParser.parse_full(input)
64    }
65
66    pub fn to_base_version_lossy(self) -> BaseVersion {
70        BaseVersion {
71            major: self.major,
72            minor: self.minor,
73        }
74    }
75
76    pub fn map<U, F>(self, fun: F) -> U
93    where
94        F: FnOnce(Self) -> U,
95    {
96        fun(self)
97    }
98}
99
100#[cfg(feature = "semver")]
101impl From<FullVersion> for semver::Version {
102    fn from(version: FullVersion) -> Self {
120        semver::Version::new(version.major, version.minor, version.patch)
121    }
122}
123
124impl From<(u64, u64, u64)> for FullVersion {
125    fn from(tuple: (u64, u64, u64)) -> Self {
126        FullVersion {
127            major: tuple.0,
128            minor: tuple.1,
129            patch: tuple.2,
130        }
131    }
132}
133
134impl fmt::Display for FullVersion {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        f.write_fmt(format_args!("{}.{}.{}", self.major, self.minor, self.patch))
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::{BaseVersion, FullVersion};
143
144    #[test]
145    fn from_tuple() {
146        let major = 0;
147        let minor = 1;
148        let patch = 2;
149
150        assert_eq!(
151            FullVersion {
152                major,
153                minor,
154                patch
155            },
156            FullVersion::from((major, minor, patch))
157        );
158    }
159
160    #[yare::parameterized(
161        zeros = { FullVersion { major: 0, minor: 0, patch: 0 }, "0.0.0" },
162        non_zero = { FullVersion { major: 1, minor: 2, patch: 3 }, "1.2.3" },
163    )]
164    fn display(base_version: FullVersion, expected: &str) {
165        let displayed = format!("{}", base_version);
166
167        assert_eq!(&displayed, expected);
168    }
169
170    #[test]
171    fn to_base_version_lossy() {
172        let full = FullVersion {
173            major: 1,
174            minor: 2,
175            patch: 3,
176        };
177        let converted = full.to_base_version_lossy();
178
179        assert_eq!(BaseVersion { major: 1, minor: 2 }, converted)
180    }
181
182    #[test]
183    fn map() {
184        let version = BaseVersion::new(1, 2);
185        let mapped = version.map(|v| ("Everything is awesome", v.major, v.minor));
186
187        assert_eq!(mapped.1, 1);
188        assert_eq!(mapped.2, 2);
189    }
190}
191
192#[cfg(test)]
193mod ord_tests {
194    use crate::FullVersion;
195    use std::cmp::Ordering;
196
197    #[yare::parameterized(
198        zero = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 0 } },
199        ones = { FullVersion { major: 1, minor: 1, patch: 1 }, FullVersion { major: 1, minor: 1, patch: 1 } },
200    )]
201    fn equals(lhs: FullVersion, rhs: FullVersion) {
202        assert_eq!(lhs.cmp(&rhs), Ordering::Equal);
203    }
204
205    #[yare::parameterized(
206        major_by_1 = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 1, minor: 0, patch: 0 } },
207        minor_by_1 = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 1, patch: 0 } },
208        patch_by_1 = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 1 } },
209    )]
210    fn less(lhs: FullVersion, rhs: FullVersion) {
211        assert_eq!(lhs.cmp(&rhs), Ordering::Less);
212    }
213
214    #[yare::parameterized(
215        major_by_1 = { FullVersion { major: 1, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 0 } },
216        minor_by_1 = { FullVersion { major: 0, minor: 1, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 0 } },
217        patch_by_1 = { FullVersion { major: 0, minor: 0, patch: 1 }, FullVersion { major: 0, minor: 0, patch: 0 } },
218    )]
219    fn greater(lhs: FullVersion, rhs: FullVersion) {
220        assert_eq!(lhs.cmp(&rhs), Ordering::Greater);
221    }
222}
223
224#[cfg(test)]
225mod partial_ord_tests {
226    use crate::FullVersion;
227    use std::cmp::Ordering;
228
229    #[yare::parameterized(
230        zero = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 0 } },
231        ones = { FullVersion { major: 1, minor: 1, patch: 1 }, FullVersion { major: 1, minor: 1, patch: 1 } },
232    )]
233    fn equals(lhs: FullVersion, rhs: FullVersion) {
234        assert_eq!(lhs.partial_cmp(&rhs), Some(Ordering::Equal));
235    }
236
237    #[yare::parameterized(
238        major_by_1 = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 1, minor: 0, patch: 0 } },
239        minor_by_1 = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 1, patch: 0 } },
240        patch_by_1 = { FullVersion { major: 0, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 1 } },
241    )]
242    fn less(lhs: FullVersion, rhs: FullVersion) {
243        assert_eq!(lhs.partial_cmp(&rhs), Some(Ordering::Less));
244    }
245
246    #[yare::parameterized(
247        major_by_1 = { FullVersion { major: 1, minor: 0, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 0 } },
248        minor_by_1 = { FullVersion { major: 0, minor: 1, patch: 0 }, FullVersion { major: 0, minor: 0, patch: 0 } },
249        patch_by_1 = { FullVersion { major: 0, minor: 0, patch: 1 }, FullVersion { major: 0, minor: 0, patch: 0 } },
250    )]
251    fn greater(lhs: FullVersion, rhs: FullVersion) {
252        assert_eq!(lhs.partial_cmp(&rhs), Some(Ordering::Greater));
253    }
254}
255
256#[cfg(test)]
257mod parse_full {
258    use crate::parsers::error::ExpectedError;
259    use crate::parsers::NumericError;
260    use crate::{FullVersion, ParserError};
261
262    #[test]
263    fn ok() {
264        let version = FullVersion::parse("1.2.3").unwrap();
265
266        assert_eq!(version, FullVersion::new(1, 2, 3));
267    }
268
269    #[test]
270    fn err_on_base_only() {
271        let result = FullVersion::parse("1.2");
272
273        assert!(matches!(
274            result.unwrap_err(),
275            ParserError::Expected(ExpectedError::Separator { .. })
276        ));
277    }
278
279    #[test]
280    fn err_on_not_finished() {
281        let result = FullVersion::parse("1.2.3.");
282
283        assert!(matches!(
284            result.unwrap_err(),
285            ParserError::Expected(ExpectedError::EndOfInput { .. })
286        ));
287    }
288
289    #[test]
290    fn err_on_starts_with_0() {
291        let result = FullVersion::parse("1.2.03");
292
293        assert!(matches!(
294            result.unwrap_err(),
295            ParserError::Numeric(NumericError::LeadingZero)
296        ));
297    }
298}