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