cuenv_release/backends/
mod.rs1use crate::artifact::PackagedArtifact;
42use crate::error::Result;
43use std::future::Future;
44use std::pin::Pin;
45
46#[derive(Debug, Clone)]
48pub struct BackendContext {
49 pub name: String,
51 pub version: String,
53 pub dry_run: bool,
55 pub download_base_url: Option<String>,
57}
58
59impl BackendContext {
60 #[must_use]
62 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
63 Self {
64 name: name.into(),
65 version: version.into(),
66 dry_run: false,
67 download_base_url: None,
68 }
69 }
70
71 #[must_use]
73 pub const fn with_dry_run(mut self, dry_run: bool) -> Self {
74 self.dry_run = dry_run;
75 self
76 }
77
78 #[must_use]
80 pub fn with_download_url(mut self, url: impl Into<String>) -> Self {
81 self.download_base_url = Some(url.into());
82 self
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct PublishResult {
89 pub backend: String,
91 pub success: bool,
93 pub url: Option<String>,
95 pub message: String,
97}
98
99impl PublishResult {
100 #[must_use]
102 pub fn success(backend: impl Into<String>, message: impl Into<String>) -> Self {
103 Self {
104 backend: backend.into(),
105 success: true,
106 url: None,
107 message: message.into(),
108 }
109 }
110
111 #[must_use]
113 pub fn success_with_url(
114 backend: impl Into<String>,
115 message: impl Into<String>,
116 url: impl Into<String>,
117 ) -> Self {
118 Self {
119 backend: backend.into(),
120 success: true,
121 url: Some(url.into()),
122 message: message.into(),
123 }
124 }
125
126 #[must_use]
128 pub fn dry_run(backend: impl Into<String>, message: impl Into<String>) -> Self {
129 Self {
130 backend: backend.into(),
131 success: true,
132 url: None,
133 message: format!("[dry-run] {}", message.into()),
134 }
135 }
136
137 #[must_use]
139 pub fn failure(backend: impl Into<String>, message: impl Into<String>) -> Self {
140 Self {
141 backend: backend.into(),
142 success: false,
143 url: None,
144 message: message.into(),
145 }
146 }
147}
148
149pub trait ReleaseBackend: Send + Sync {
163 fn name(&self) -> &'static str;
165
166 fn publish<'a>(
175 &'a self,
176 ctx: &'a BackendContext,
177 artifacts: &'a [PackagedArtifact],
178 ) -> Pin<Box<dyn Future<Output = Result<PublishResult>> + Send + 'a>>;
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_backend_context_new() {
187 let ctx = BackendContext::new("my-app", "1.0.0");
188 assert_eq!(ctx.name, "my-app");
189 assert_eq!(ctx.version, "1.0.0");
190 assert!(!ctx.dry_run);
191 assert!(ctx.download_base_url.is_none());
192 }
193
194 #[test]
195 fn test_backend_context_with_dry_run() {
196 let ctx = BackendContext::new("my-app", "1.0.0").with_dry_run(true);
197 assert!(ctx.dry_run);
198 }
199
200 #[test]
201 fn test_backend_context_with_download_url() {
202 let ctx =
203 BackendContext::new("my-app", "1.0.0").with_download_url("https://github.com/releases");
204 assert_eq!(
205 ctx.download_base_url,
206 Some("https://github.com/releases".to_string())
207 );
208 }
209
210 #[test]
211 fn test_backend_context_builder_chain() {
212 let ctx = BackendContext::new("test", "2.0.0")
213 .with_dry_run(true)
214 .with_download_url("https://example.com");
215
216 assert_eq!(ctx.name, "test");
217 assert_eq!(ctx.version, "2.0.0");
218 assert!(ctx.dry_run);
219 assert_eq!(
220 ctx.download_base_url,
221 Some("https://example.com".to_string())
222 );
223 }
224
225 #[test]
226 fn test_publish_result_success() {
227 let result = PublishResult::success("github", "Published successfully");
228 assert!(result.success);
229 assert_eq!(result.backend, "github");
230 assert_eq!(result.message, "Published successfully");
231 assert!(result.url.is_none());
232 }
233
234 #[test]
235 fn test_publish_result_success_with_url() {
236 let result = PublishResult::success_with_url(
237 "github",
238 "Released",
239 "https://github.com/repo/releases/v1.0.0",
240 );
241 assert!(result.success);
242 assert_eq!(
243 result.url,
244 Some("https://github.com/repo/releases/v1.0.0".to_string())
245 );
246 }
247
248 #[test]
249 fn test_publish_result_dry_run() {
250 let result = PublishResult::dry_run("homebrew", "Would update formula");
251 assert!(result.success);
252 assert!(result.message.starts_with("[dry-run]"));
253 assert!(result.message.contains("Would update formula"));
254 }
255
256 #[test]
257 fn test_publish_result_failure() {
258 let result = PublishResult::failure("crates-io", "Upload failed");
259 assert!(!result.success);
260 assert_eq!(result.backend, "crates-io");
261 assert_eq!(result.message, "Upload failed");
262 assert!(result.url.is_none());
263 }
264
265 #[test]
266 fn test_backend_context_debug() {
267 let ctx = BackendContext::new("app", "1.0");
268 let debug_str = format!("{ctx:?}");
269 assert!(debug_str.contains("BackendContext"));
270 assert!(debug_str.contains("app"));
271 }
272
273 #[test]
274 fn test_publish_result_debug() {
275 let result = PublishResult::success("test", "ok");
276 let debug_str = format!("{result:?}");
277 assert!(debug_str.contains("PublishResult"));
278 assert!(debug_str.contains("test"));
279 }
280
281 #[test]
282 fn test_backend_context_clone() {
283 let ctx = BackendContext::new("app", "1.0")
284 .with_dry_run(true)
285 .with_download_url("https://example.com");
286 let cloned = ctx.clone();
287 assert_eq!(ctx.name, cloned.name);
288 assert_eq!(ctx.version, cloned.version);
289 assert_eq!(ctx.dry_run, cloned.dry_run);
290 assert_eq!(ctx.download_base_url, cloned.download_base_url);
291 }
292
293 #[test]
294 fn test_publish_result_clone() {
295 let result = PublishResult::success_with_url("github", "Released", "https://url");
296 let cloned = result.clone();
297 assert_eq!(result.backend, cloned.backend);
298 assert_eq!(result.success, cloned.success);
299 assert_eq!(result.url, cloned.url);
300 assert_eq!(result.message, cloned.message);
301 }
302}