1//! Constructs for working with [semantic versions](https://semver.org/).
2
3#![deny(missing_docs)]
4
5use std::{
6 fmt::{self, Display},
7 str::FromStr,
8};
9
10use anyhow::{Context as _, Result};
11use serde::{Deserialize, Serialize, de::Error};
12
13/// A [semantic version](https://semver.org/) number.
14#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
15pub struct SemanticVersion {
16 major: usize,
17 minor: usize,
18 patch: usize,
19}
20
21impl SemanticVersion {
22 /// Returns a new [`SemanticVersion`] from the given components.
23 pub const fn new(major: usize, minor: usize, patch: usize) -> Self {
24 Self {
25 major,
26 minor,
27 patch,
28 }
29 }
30
31 /// Returns the major version number.
32 #[inline(always)]
33 pub fn major(&self) -> usize {
34 self.major
35 }
36
37 /// Returns the minor version number.
38 #[inline(always)]
39 pub fn minor(&self) -> usize {
40 self.minor
41 }
42
43 /// Returns the patch version number.
44 #[inline(always)]
45 pub fn patch(&self) -> usize {
46 self.patch
47 }
48}
49
50impl FromStr for SemanticVersion {
51 type Err = anyhow::Error;
52
53 fn from_str(s: &str) -> Result<Self> {
54 let mut components = s.trim().split('.');
55 let major = components
56 .next()
57 .context("missing major version number")?
58 .parse()?;
59 let minor = components
60 .next()
61 .context("missing minor version number")?
62 .parse()?;
63 let patch = components
64 .next()
65 .context("missing patch version number")?
66 .parse()?;
67 Ok(Self {
68 major,
69 minor,
70 patch,
71 })
72 }
73}
74
75impl Display for SemanticVersion {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
78 }
79}
80
81impl Serialize for SemanticVersion {
82 fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
83 where
84 S: serde::Serializer,
85 {
86 serializer.serialize_str(&self.to_string())
87 }
88}
89
90impl<'de> Deserialize<'de> for SemanticVersion {
91 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92 where
93 D: serde::Deserializer<'de>,
94 {
95 let string = String::deserialize(deserializer)?;
96 Self::from_str(&string)
97 .map_err(|_| Error::custom(format!("Invalid version string \"{string}\"")))
98 }
99}