libcnb_data/buildpack/
version.rs1use serde::Deserialize;
2use std::fmt;
3use std::fmt::{Display, Formatter};
4
5#[derive(Deserialize, Debug, Eq, PartialEq)]
10#[serde(try_from = "String")]
11pub struct BuildpackVersion {
12 pub major: u64,
13 pub minor: u64,
14 pub patch: u64,
15}
16
17impl BuildpackVersion {
18 #[must_use]
19 pub fn new(major: u64, minor: u64, patch: u64) -> Self {
20 Self {
21 major,
22 minor,
23 patch,
24 }
25 }
26}
27
28impl TryFrom<String> for BuildpackVersion {
29 type Error = BuildpackVersionError;
30
31 fn try_from(value: String) -> Result<Self, Self::Error> {
32 match value
35 .split('.')
36 .map(|s| {
37 if s.starts_with('0') && s != "0" {
39 None
40 } else {
41 s.parse().ok()
42 }
43 })
44 .collect::<Option<Vec<_>>>()
45 .unwrap_or_default()
46 .as_slice()
47 {
48 &[major, minor, patch] => Ok(Self::new(major, minor, patch)),
49 _ => Err(Self::Error::InvalidBuildpackVersion(value)),
50 }
51 }
52}
53
54impl Display for BuildpackVersion {
55 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
56 formatter.write_str(&format!("{}.{}.{}", self.major, self.minor, self.patch))
57 }
58}
59
60#[derive(thiserror::Error, Debug)]
61pub enum BuildpackVersionError {
62 #[error("Invalid buildpack version: `{0}`")]
63 InvalidBuildpackVersion(String),
64}
65
66#[cfg(test)]
67mod tests {
68 use serde_test::{Token, assert_de_tokens, assert_de_tokens_error};
69
70 use super::*;
71
72 #[test]
73 fn deserialize_valid_versions() {
74 assert_de_tokens(
75 &BuildpackVersion {
76 major: 1,
77 minor: 3,
78 patch: 4,
79 },
80 &[Token::BorrowedStr("1.3.4")],
81 );
82 assert_de_tokens(
83 &BuildpackVersion {
84 major: 0,
85 minor: 0,
86 patch: 0,
87 },
88 &[Token::BorrowedStr("0.0.0")],
89 );
90 assert_de_tokens(
91 &BuildpackVersion {
92 major: 1234,
93 minor: 5678,
94 patch: 9876,
95 },
96 &[Token::BorrowedStr("1234.5678.9876")],
97 );
98 }
99
100 #[test]
101 fn reject_wrong_number_of_version_parts() {
102 assert_de_tokens_error::<BuildpackVersion>(
103 &[Token::BorrowedStr("")],
104 "Invalid buildpack version: ``",
105 );
106 assert_de_tokens_error::<BuildpackVersion>(
107 &[Token::BorrowedStr("12345")],
108 "Invalid buildpack version: `12345`",
109 );
110 assert_de_tokens_error::<BuildpackVersion>(
111 &[Token::BorrowedStr("1.2")],
112 "Invalid buildpack version: `1.2`",
113 );
114 assert_de_tokens_error::<BuildpackVersion>(
115 &[Token::BorrowedStr("1.2.3.4")],
116 "Invalid buildpack version: `1.2.3.4`",
117 );
118 assert_de_tokens_error::<BuildpackVersion>(
119 &[Token::BorrowedStr(".2.3")],
120 "Invalid buildpack version: `.2.3`",
121 );
122 assert_de_tokens_error::<BuildpackVersion>(
123 &[Token::BorrowedStr("1.2.")],
124 "Invalid buildpack version: `1.2.`",
125 );
126 assert_de_tokens_error::<BuildpackVersion>(
127 &[Token::BorrowedStr("1..3")],
128 "Invalid buildpack version: `1..3`",
129 );
130 assert_de_tokens_error::<BuildpackVersion>(
131 &[Token::BorrowedStr("1..2.3")],
132 "Invalid buildpack version: `1..2.3`",
133 );
134 assert_de_tokens_error::<BuildpackVersion>(
135 &[Token::BorrowedStr("1.2_3")],
136 "Invalid buildpack version: `1.2_3`",
137 );
138 }
139
140 #[test]
141 fn reject_version_suffixes() {
142 assert_de_tokens_error::<BuildpackVersion>(
144 &[Token::BorrowedStr("1.2.3-dev")],
145 "Invalid buildpack version: `1.2.3-dev`",
146 );
147 assert_de_tokens_error::<BuildpackVersion>(
148 &[Token::BorrowedStr("1.2.3+abc")],
149 "Invalid buildpack version: `1.2.3+abc`",
150 );
151 }
152
153 #[test]
154 fn reject_negative_versions() {
155 assert_de_tokens_error::<BuildpackVersion>(
156 &[Token::BorrowedStr("-1.2.3")],
157 "Invalid buildpack version: `-1.2.3`",
158 );
159 assert_de_tokens_error::<BuildpackVersion>(
160 &[Token::BorrowedStr("1.-2.3")],
161 "Invalid buildpack version: `1.-2.3`",
162 );
163 assert_de_tokens_error::<BuildpackVersion>(
164 &[Token::BorrowedStr("1.2.-3")],
165 "Invalid buildpack version: `1.2.-3`",
166 );
167 }
168
169 #[test]
170 fn reject_versions_with_leading_zeros() {
171 assert_de_tokens_error::<BuildpackVersion>(
172 &[Token::BorrowedStr("01.2.3")],
173 "Invalid buildpack version: `01.2.3`",
174 );
175 assert_de_tokens_error::<BuildpackVersion>(
176 &[Token::BorrowedStr("1.00.3")],
177 "Invalid buildpack version: `1.00.3`",
178 );
179 assert_de_tokens_error::<BuildpackVersion>(
180 &[Token::BorrowedStr("1.2.030")],
181 "Invalid buildpack version: `1.2.030`",
182 );
183 }
184
185 #[test]
186 fn buildpack_version_display() {
187 assert_eq!(
188 BuildpackVersion {
189 major: 0,
190 minor: 1,
191 patch: 2,
192 }
193 .to_string(),
194 "0.1.2"
195 );
196 assert_eq!(
197 BuildpackVersion {
198 major: 2000,
199 minor: 10,
200 patch: 20,
201 }
202 .to_string(),
203 "2000.10.20"
204 );
205 }
206}