xianyun_deploy/registrycache/
refs.rs1use std::fs::{self, OpenOptions};
6use std::io;
7
8use thiserror::Error;
9
10use super::paths::{self, PathError};
11
12#[derive(Debug, Error)]
14pub enum RefsError {
15 #[error(transparent)]
17 Path(#[from] PathError),
18 #[error("creating refs dir: {0}")]
20 CreateDir(io::Error),
21 #[error("creating ref {0}: {1}")]
23 CreateRef(String, io::Error),
24 #[error("removing ref {0}: {1}")]
26 RemoveRef(String, io::Error),
27 #[error("reading refs dir: {0}")]
29 ReadDir(io::Error),
30}
31
32pub fn add_ref(profile: &str) -> Result<(), RefsError> {
35 let refs_dir = paths::refs_dir()?;
36 fs::create_dir_all(&refs_dir).map_err(RefsError::CreateDir)?;
37
38 let path = paths::ref_path(profile)?;
39 OpenOptions::new()
40 .create(true)
41 .truncate(false)
42 .write(true)
43 .open(&path)
44 .map_err(|e| RefsError::CreateRef(profile.to_string(), e))?;
45
46 Ok(())
47}
48
49pub fn remove_ref(profile: &str) -> Result<(), RefsError> {
52 let path = paths::ref_path(profile)?;
53
54 match fs::remove_file(&path) {
55 Ok(()) => Ok(()),
56 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
57 Err(e) => Err(RefsError::RemoveRef(profile.to_string(), e)),
58 }
59}
60
61pub fn list_refs() -> Result<Vec<String>, RefsError> {
64 let refs_dir = paths::refs_dir()?;
65
66 let entries = match fs::read_dir(&refs_dir) {
67 Ok(entries) => entries,
68 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
69 Err(e) => return Err(RefsError::ReadDir(e)),
70 };
71
72 let mut out = Vec::new();
73 for entry in entries {
74 let entry = entry.map_err(RefsError::ReadDir)?;
75 let file_type = entry.file_type().map_err(RefsError::ReadDir)?;
76 if file_type.is_file() {
77 out.push(entry.file_name().to_string_lossy().into_owned());
78 }
79 }
80
81 out.sort();
82
83 Ok(out)
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::registrycache::test_support::with_home;
90
91 #[test]
93 fn test_add_ref_creates_file() {
94 with_home(|_home| {
95 add_ref("alpha").unwrap();
96
97 let path = paths::ref_path("alpha").unwrap();
98 assert!(path.exists());
99 });
100 }
101
102 #[test]
104 fn test_add_ref_idempotent() {
105 with_home(|_home| {
106 add_ref("alpha").unwrap();
107 add_ref("alpha").unwrap();
108
109 let refs = list_refs().unwrap();
110 assert_eq!(refs, vec!["alpha".to_string()]);
111 });
112 }
113
114 #[test]
116 fn test_remove_ref_removes_file() {
117 with_home(|_home| {
118 add_ref("alpha").unwrap();
119 remove_ref("alpha").unwrap();
120
121 let path = paths::ref_path("alpha").unwrap();
122 assert!(!path.exists());
123 });
124 }
125
126 #[test]
128 fn test_remove_ref_missing_is_noop() {
129 with_home(|_home| {
130 remove_ref("alpha").unwrap();
131 });
132 }
133
134 #[test]
136 fn test_list_refs_empty() {
137 with_home(|_home| {
138 let refs = list_refs().unwrap();
139 assert!(refs.is_empty());
140 });
141 }
142
143 #[test]
145 fn test_list_refs_multiple_sorted() {
146 with_home(|_home| {
147 add_ref("charlie").unwrap();
148 add_ref("alpha").unwrap();
149 add_ref("bravo").unwrap();
150
151 let refs = list_refs().unwrap();
152 assert_eq!(
153 refs,
154 vec![
155 "alpha".to_string(),
156 "bravo".to_string(),
157 "charlie".to_string()
158 ]
159 );
160 });
161 }
162
163 #[test]
165 fn test_refs_dir_is_created() {
166 with_home(|_home| {
167 add_ref("alpha").unwrap();
168
169 let refs_dir = paths::refs_dir().unwrap();
170 assert!(refs_dir.is_dir());
171
172 let parent = refs_dir.parent().unwrap();
173 assert!(parent.is_dir());
174 });
175 }
176}