changeset_project/
release_state.rs1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
13pub struct PrereleaseState {
14 #[serde(flatten)]
15 packages: HashMap<String, String>,
16}
17
18impl PrereleaseState {
19 #[must_use]
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 #[must_use]
25 pub fn get(&self, crate_name: &str) -> Option<&str> {
26 self.packages.get(crate_name).map(String::as_str)
27 }
28
29 pub fn insert(&mut self, crate_name: String, tag: String) {
30 self.packages.insert(crate_name, tag);
31 }
32
33 #[must_use]
34 pub fn remove(&mut self, crate_name: &str) -> Option<String> {
35 self.packages.remove(crate_name)
36 }
37
38 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
39 self.packages.iter().map(|(k, v)| (k.as_str(), v.as_str()))
40 }
41
42 #[must_use]
43 pub fn is_empty(&self) -> bool {
44 self.packages.is_empty()
45 }
46
47 #[must_use]
48 pub fn contains(&self, crate_name: &str) -> bool {
49 self.packages.contains_key(crate_name)
50 }
51
52 #[must_use]
53 pub fn len(&self) -> usize {
54 self.packages.len()
55 }
56}
57
58#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
65pub struct GraduationState {
66 #[serde(default)]
67 graduation: Vec<String>,
68}
69
70impl GraduationState {
71 #[must_use]
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 pub fn add(&mut self, crate_name: String) {
77 if !self.graduation.contains(&crate_name) {
78 self.graduation.push(crate_name);
79 }
80 }
81
82 #[must_use]
83 pub fn remove(&mut self, crate_name: &str) -> bool {
84 let len_before = self.graduation.len();
85 self.graduation.retain(|x| x != crate_name);
86 self.graduation.len() != len_before
87 }
88
89 #[must_use]
90 pub fn contains(&self, crate_name: &str) -> bool {
91 self.graduation.iter().any(|x| x == crate_name)
92 }
93
94 pub fn iter(&self) -> impl Iterator<Item = &str> {
95 self.graduation.iter().map(String::as_str)
96 }
97
98 #[must_use]
99 pub fn is_empty(&self) -> bool {
100 self.graduation.is_empty()
101 }
102
103 #[must_use]
104 pub fn len(&self) -> usize {
105 self.graduation.len()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 mod prerelease_state {
114 use super::*;
115
116 #[test]
117 fn new_creates_empty_state() {
118 let state = PrereleaseState::new();
119
120 assert!(state.is_empty());
121 assert_eq!(state.len(), 0);
122 }
123
124 #[test]
125 fn insert_and_get() {
126 let mut state = PrereleaseState::new();
127
128 state.insert("my-crate".to_string(), "alpha".to_string());
129
130 assert_eq!(state.get("my-crate"), Some("alpha"));
131 assert_eq!(state.len(), 1);
132 assert!(!state.is_empty());
133 }
134
135 #[test]
136 fn insert_overwrites_existing() {
137 let mut state = PrereleaseState::new();
138 state.insert("my-crate".to_string(), "alpha".to_string());
139
140 state.insert("my-crate".to_string(), "beta".to_string());
141
142 assert_eq!(state.get("my-crate"), Some("beta"));
143 assert_eq!(state.len(), 1);
144 }
145
146 #[test]
147 fn get_nonexistent_returns_none() {
148 let state = PrereleaseState::new();
149
150 assert_eq!(state.get("nonexistent"), None);
151 }
152
153 #[test]
154 fn remove_existing() {
155 let mut state = PrereleaseState::new();
156 state.insert("my-crate".to_string(), "alpha".to_string());
157
158 let removed = state.remove("my-crate");
159
160 assert_eq!(removed, Some("alpha".to_string()));
161 assert!(state.is_empty());
162 }
163
164 #[test]
165 fn remove_nonexistent_returns_none() {
166 let mut state = PrereleaseState::new();
167
168 let removed = state.remove("nonexistent");
169
170 assert_eq!(removed, None);
171 }
172
173 #[test]
174 fn contains() {
175 let mut state = PrereleaseState::new();
176 state.insert("my-crate".to_string(), "alpha".to_string());
177
178 assert!(state.contains("my-crate"));
179 assert!(!state.contains("other-crate"));
180 }
181
182 #[test]
183 fn iter() {
184 let mut state = PrereleaseState::new();
185 state.insert("crate-a".to_string(), "alpha".to_string());
186 state.insert("crate-b".to_string(), "beta".to_string());
187
188 let items: Vec<_> = state.iter().collect();
189
190 assert_eq!(items.len(), 2);
191 assert!(items.contains(&("crate-a", "alpha")));
192 assert!(items.contains(&("crate-b", "beta")));
193 }
194
195 #[test]
196 fn serialize_deserialize_roundtrip() {
197 let mut state = PrereleaseState::new();
198 state.insert("crate-a".to_string(), "alpha".to_string());
199 state.insert("crate-b".to_string(), "beta".to_string());
200
201 let serialized = toml::to_string(&state).expect("serialization should succeed");
202 let deserialized: PrereleaseState =
203 toml::from_str(&serialized).expect("deserialization should succeed");
204
205 assert_eq!(state, deserialized);
206 }
207
208 #[test]
209 fn deserialize_from_toml() {
210 let toml_content = r#"
211crate-a = "alpha"
212crate-b = "beta"
213"#;
214
215 let state: PrereleaseState =
216 toml::from_str(toml_content).expect("deserialization should succeed");
217
218 assert_eq!(state.get("crate-a"), Some("alpha"));
219 assert_eq!(state.get("crate-b"), Some("beta"));
220 assert_eq!(state.len(), 2);
221 }
222
223 #[test]
224 fn deserialize_empty() {
225 let toml_content = "";
226
227 let state: PrereleaseState =
228 toml::from_str(toml_content).expect("deserialization should succeed");
229
230 assert!(state.is_empty());
231 }
232 }
233
234 mod graduation_state {
235 use super::*;
236
237 #[test]
238 fn new_creates_empty_state() {
239 let state = GraduationState::new();
240
241 assert!(state.is_empty());
242 assert_eq!(state.len(), 0);
243 }
244
245 #[test]
246 fn add_single() {
247 let mut state = GraduationState::new();
248
249 state.add("my-crate".to_string());
250
251 assert!(state.contains("my-crate"));
252 assert_eq!(state.len(), 1);
253 }
254
255 #[test]
256 fn add_duplicate_is_ignored() {
257 let mut state = GraduationState::new();
258 state.add("my-crate".to_string());
259
260 state.add("my-crate".to_string());
261
262 assert_eq!(state.len(), 1);
263 }
264
265 #[test]
266 fn add_multiple() {
267 let mut state = GraduationState::new();
268
269 state.add("crate-a".to_string());
270 state.add("crate-b".to_string());
271
272 assert_eq!(state.len(), 2);
273 assert!(state.contains("crate-a"));
274 assert!(state.contains("crate-b"));
275 }
276
277 #[test]
278 fn remove_existing() {
279 let mut state = GraduationState::new();
280 state.add("my-crate".to_string());
281
282 let removed = state.remove("my-crate");
283
284 assert!(removed);
285 assert!(state.is_empty());
286 }
287
288 #[test]
289 fn remove_nonexistent_returns_false() {
290 let mut state = GraduationState::new();
291
292 let removed = state.remove("nonexistent");
293
294 assert!(!removed);
295 }
296
297 #[test]
298 fn contains() {
299 let mut state = GraduationState::new();
300 state.add("my-crate".to_string());
301
302 assert!(state.contains("my-crate"));
303 assert!(!state.contains("other-crate"));
304 }
305
306 #[test]
307 fn iter() {
308 let mut state = GraduationState::new();
309 state.add("crate-a".to_string());
310 state.add("crate-b".to_string());
311
312 let items: Vec<_> = state.iter().collect();
313
314 assert_eq!(items.len(), 2);
315 assert!(items.contains(&"crate-a"));
316 assert!(items.contains(&"crate-b"));
317 }
318
319 #[test]
320 fn serialize_deserialize_roundtrip() {
321 let mut state = GraduationState::new();
322 state.add("crate-a".to_string());
323 state.add("crate-b".to_string());
324
325 let serialized = toml::to_string(&state).expect("serialization should succeed");
326 let deserialized: GraduationState =
327 toml::from_str(&serialized).expect("deserialization should succeed");
328
329 assert_eq!(state, deserialized);
330 }
331
332 #[test]
333 fn deserialize_from_toml() {
334 let toml_content = r#"
335graduation = ["crate-a", "crate-b"]
336"#;
337
338 let state: GraduationState =
339 toml::from_str(toml_content).expect("deserialization should succeed");
340
341 assert!(state.contains("crate-a"));
342 assert!(state.contains("crate-b"));
343 assert_eq!(state.len(), 2);
344 }
345
346 #[test]
347 fn deserialize_empty() {
348 let toml_content = "";
349
350 let state: GraduationState =
351 toml::from_str(toml_content).expect("deserialization should succeed");
352
353 assert!(state.is_empty());
354 }
355
356 #[test]
357 fn deserialize_empty_array() {
358 let toml_content = "graduation = []";
359
360 let state: GraduationState =
361 toml::from_str(toml_content).expect("deserialization should succeed");
362
363 assert!(state.is_empty());
364 }
365 }
366}