Skip to main content

rabbitmq_versioning/
version.rs

1// Copyright (c) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::cmp::Ordering;
10use std::fmt;
11use std::str::FromStr;
12
13use serde::{Deserialize, Serialize};
14
15use crate::errors::Error;
16use crate::prerelease::Prerelease;
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct Version {
20    pub major: u32,
21    pub minor: u32,
22    pub patch: u32,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub prerelease: Option<Prerelease>,
25}
26
27impl Version {
28    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
29        Self {
30            major,
31            minor,
32            patch,
33            prerelease: None,
34        }
35    }
36
37    pub fn with_prerelease(major: u32, minor: u32, patch: u32, prerelease: Prerelease) -> Self {
38        Self {
39            major,
40            minor,
41            patch,
42            prerelease: Some(prerelease),
43        }
44    }
45
46    pub fn dir_name(&self) -> String {
47        self.to_string()
48    }
49
50    pub fn is_ga(&self) -> bool {
51        self.prerelease.is_none()
52    }
53
54    pub fn is_prerelease(&self) -> bool {
55        self.prerelease.is_some()
56    }
57
58    pub fn is_alpha(&self) -> bool {
59        self.prerelease.as_ref().is_some_and(|p| p.is_alpha())
60    }
61
62    pub fn is_beta(&self) -> bool {
63        self.prerelease.as_ref().is_some_and(|p| p.is_beta())
64    }
65
66    pub fn is_rc(&self) -> bool {
67        self.prerelease.as_ref().is_some_and(|p| p.is_rc())
68    }
69
70    pub fn is_distributed_via_server_packages_repository(&self) -> bool {
71        self.is_alpha()
72    }
73
74    pub fn download_url(&self) -> String {
75        format!(
76            "https://github.com/rabbitmq/rabbitmq-server/releases/download/v{v}/rabbitmq-server-generic-unix-{v}.tar.xz",
77            v = self
78        )
79    }
80
81    pub fn download_url_with_tag(&self, tag: &str) -> String {
82        format!(
83            "https://github.com/rabbitmq/server-packages/releases/download/{tag}/rabbitmq-server-generic-unix-{v}.tar.xz",
84            tag = tag,
85            v = self
86        )
87    }
88
89    pub fn archive_name(&self) -> String {
90        format!("rabbitmq-server-generic-unix-{}.tar.xz", self)
91    }
92
93    pub fn extracted_dir_name(&self) -> String {
94        format!("rabbitmq_server-{}", self)
95    }
96
97    pub fn base_version(&self) -> Version {
98        Version::new(self.major, self.minor, self.patch)
99    }
100}
101
102impl fmt::Display for Version {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
105        if let Some(ref pre) = self.prerelease {
106            write!(f, "-{}", pre)?;
107        }
108        Ok(())
109    }
110}
111
112impl FromStr for Version {
113    type Err = Error;
114
115    fn from_str(s: &str) -> Result<Self, Self::Err> {
116        let s = s.trim().trim_start_matches('v');
117
118        let (version_part, prerelease) = if let Some(idx) = s.find('-') {
119            let (ver, pre) = s.split_at(idx);
120            let pre = &pre[1..];
121            (ver, Some(Prerelease::parse(pre, s)?))
122        } else {
123            (s, None)
124        };
125
126        let parts: Vec<&str> = version_part.split('.').collect();
127
128        if parts.len() != 3 {
129            return Err(Error::InvalidVersion(s.to_string()));
130        }
131
132        let major = parts[0]
133            .parse()
134            .map_err(|_| Error::InvalidVersion(s.to_string()))?;
135        let minor = parts[1]
136            .parse()
137            .map_err(|_| Error::InvalidVersion(s.to_string()))?;
138        let patch = parts[2]
139            .parse()
140            .map_err(|_| Error::InvalidVersion(s.to_string()))?;
141
142        Ok(Version {
143            major,
144            minor,
145            patch,
146            prerelease,
147        })
148    }
149}
150
151impl Ord for Version {
152    fn cmp(&self, other: &Self) -> Ordering {
153        let base =
154            (self.major, self.minor, self.patch).cmp(&(other.major, other.minor, other.patch));
155        if base != Ordering::Equal {
156            return base;
157        }
158
159        match (&self.prerelease, &other.prerelease) {
160            (None, None) => Ordering::Equal,
161            (Some(_), None) => Ordering::Less,
162            (None, Some(_)) => Ordering::Greater,
163            (Some(a), Some(b)) => a.cmp(b),
164        }
165    }
166}
167
168impl PartialOrd for Version {
169    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
170        Some(self.cmp(other))
171    }
172}