1use std::io::Write;
2use std::sync::Arc;
3
4use crate::api_traits::{Deploy, DeployAsset, Timestamp};
5use crate::cli::release::{ReleaseAssetOptions, ReleaseOptions};
6use crate::cmds::common::num_release_pages;
7use crate::config::ConfigProperties;
8use crate::display::{Column, DisplayBody};
9use crate::remote::{self, CacheType, ListBodyArgs, ListRemoteCliArgs};
10use crate::Result;
11
12use super::common::{
13 self, num_release_asset_pages, num_release_asset_resources, num_release_resources,
14};
15
16#[derive(Builder, Clone)]
17pub struct ReleaseBodyArgs {
18 pub from_to_page: Option<ListBodyArgs>,
19}
20
21impl ReleaseBodyArgs {
22 pub fn builder() -> ReleaseBodyArgsBuilder {
23 ReleaseBodyArgsBuilder::default()
24 }
25}
26
27#[derive(Builder, Clone)]
28pub struct Release {
29 id: String,
30 url: String,
31 tag: String,
32 title: String,
33 description: String,
34 #[builder(default)]
35 prerelease: bool,
36 created_at: String,
37 updated_at: String,
38}
39
40impl Release {
41 pub fn builder() -> ReleaseBuilder {
42 ReleaseBuilder::default()
43 }
44}
45
46impl From<Release> for DisplayBody {
47 fn from(release: Release) -> Self {
48 DisplayBody::new(vec![
49 Column::new("ID", release.id),
50 Column::new("Tag", release.tag),
51 Column::new("Title", release.title),
52 Column::new("Description", release.description),
53 Column::new("URL", release.url),
54 Column::new("Prerelease", release.prerelease.to_string()),
55 Column::new("Created At", release.created_at),
56 Column::new("Updated At", release.updated_at),
57 ])
58 }
59}
60
61impl Timestamp for Release {
62 fn created_at(&self) -> String {
63 self.created_at.clone()
64 }
65}
66
67#[derive(Builder, Clone)]
68pub struct ReleaseAssetListCliArgs {
69 pub id: String,
70 pub list_args: ListRemoteCliArgs,
71}
72
73impl ReleaseAssetListCliArgs {
74 pub fn builder() -> ReleaseAssetListCliArgsBuilder {
75 ReleaseAssetListCliArgsBuilder::default()
76 }
77}
78
79#[derive(Builder, Clone)]
80pub struct ReleaseAssetListBodyArgs {
81 pub id: String,
83 pub list_args: Option<ListBodyArgs>,
84}
85
86impl ReleaseAssetListBodyArgs {
87 pub fn builder() -> ReleaseAssetListBodyArgsBuilder {
88 ReleaseAssetListBodyArgsBuilder::default()
89 }
90}
91
92#[derive(Builder, Clone)]
93pub struct ReleaseAssetMetadata {
94 id: String,
95 name: String,
96 url: String,
97 size: String,
98 created_at: String,
99 updated_at: String,
100}
101
102impl ReleaseAssetMetadata {
103 pub fn builder() -> ReleaseAssetMetadataBuilder {
104 ReleaseAssetMetadataBuilder::default()
105 }
106}
107
108impl From<ReleaseAssetMetadata> for DisplayBody {
109 fn from(asset: ReleaseAssetMetadata) -> Self {
110 DisplayBody::new(vec![
111 Column::new("ID", asset.id),
112 Column::new("Name", asset.name),
113 Column::new("URL", asset.url),
114 Column::new("Size", asset.size),
115 Column::new("Created At", asset.created_at),
116 Column::new("Updated At", asset.updated_at),
117 ])
118 }
119}
120
121impl Timestamp for ReleaseAssetMetadata {
122 fn created_at(&self) -> String {
123 self.created_at.clone()
124 }
125}
126
127pub fn execute(
128 options: ReleaseOptions,
129 config: Arc<dyn ConfigProperties>,
130 domain: String,
131 path: String,
132) -> Result<()> {
133 match options {
134 ReleaseOptions::List(cli_args) => {
135 let remote = crate::remote::get_deploy(
136 domain,
137 path,
138 config,
139 Some(&cli_args.get_args.cache_args),
140 CacheType::File,
141 )?;
142 if cli_args.num_pages {
143 return num_release_pages(remote, std::io::stdout());
144 }
145 if cli_args.num_resources {
146 return num_release_resources(remote, std::io::stdout());
147 }
148 let from_to_args = remote::validate_from_to_page(&cli_args)?;
149 let body_args = ReleaseBodyArgs::builder()
150 .from_to_page(from_to_args)
151 .build()?;
152 list_releases(remote, body_args, cli_args, std::io::stdout())
153 }
154 ReleaseOptions::Assets(cli_opts) => match cli_opts {
155 ReleaseAssetOptions::List(cli_args) => {
156 let remote = crate::remote::get_deploy_asset(
157 domain,
158 path,
159 config,
160 Some(&cli_args.list_args.get_args.cache_args),
161 CacheType::File,
162 )?;
163
164 let list_args = remote::validate_from_to_page(&cli_args.list_args)?;
165 let body_args = ReleaseAssetListBodyArgs::builder()
166 .id(cli_args.id.clone())
167 .list_args(list_args)
168 .build()?;
169 if cli_args.list_args.num_pages {
170 return num_release_asset_pages(remote, body_args, std::io::stdout());
171 }
172 if cli_args.list_args.num_resources {
173 return num_release_asset_resources(remote, body_args, std::io::stdout());
174 }
175 list_release_assets(remote, body_args, cli_args, std::io::stdout())
176 }
177 },
178 }
179}
180
181fn list_releases<W: Write>(
182 remote: Arc<dyn Deploy>,
183 body_args: ReleaseBodyArgs,
184 cli_args: ListRemoteCliArgs,
185 mut writer: W,
186) -> Result<()> {
187 common::list_releases(remote, body_args, cli_args, &mut writer)
188}
189
190fn list_release_assets<W: Write>(
191 remote: Arc<dyn DeployAsset>,
192 body_args: ReleaseAssetListBodyArgs,
193 cli_args: ReleaseAssetListCliArgs,
194 mut writer: W,
195) -> Result<()> {
196 common::list_release_assets(remote, body_args, cli_args, &mut writer)
197}
198
199#[cfg(test)]
200mod test {
201 use crate::api_traits::NumberDeltaErr;
202
203 use super::*;
204
205 struct MockDeploy {
206 empty_releases: bool,
207 }
208
209 impl MockDeploy {
210 fn new(empty_releases: bool) -> Self {
211 Self { empty_releases }
212 }
213 }
214
215 impl Deploy for MockDeploy {
216 fn list(&self, _args: ReleaseBodyArgs) -> Result<Vec<Release>> {
217 if self.empty_releases {
218 return Ok(vec![]);
219 }
220 Ok(vec![Release::builder()
221 .id(String::from("1"))
222 .url(String::from(
223 "https://github.com/jordilin/githapi/releases/tag/v0.1.20",
224 ))
225 .tag(String::from("v1.0.0"))
226 .title(String::from("First release"))
227 .description(String::from("Initial release"))
228 .created_at(String::from("2021-01-01T00:00:00Z"))
229 .updated_at(String::from("2021-01-01T00:00:01Z"))
230 .build()
231 .unwrap()])
232 }
233
234 fn num_pages(&self) -> Result<Option<u32>> {
235 todo!()
236 }
237
238 fn num_resources(&self) -> Result<Option<NumberDeltaErr>> {
239 todo!()
240 }
241 }
242
243 #[test]
244 fn test_list_releases() {
245 let remote = Arc::new(MockDeploy::new(false));
246 let body_args = ReleaseBodyArgs::builder()
247 .from_to_page(None)
248 .build()
249 .unwrap();
250 let cli_args = ListRemoteCliArgs::builder().build().unwrap();
251 let mut writer = Vec::new();
252 list_releases(remote, body_args, cli_args, &mut writer).unwrap();
253 assert_eq!(
254 "ID|Tag|Title|Description|URL|Prerelease|Created At|Updated At\n1|v1.0.0|First release|Initial release|https://github.com/jordilin/githapi/releases/tag/v0.1.20|false|2021-01-01T00:00:00Z|2021-01-01T00:00:01Z\n",
255 String::from_utf8(writer).unwrap(),
256 );
257 }
258
259 #[test]
260 fn test_no_releases_found() {
261 let remote = Arc::new(MockDeploy::new(true));
262 let body_args = ReleaseBodyArgs::builder()
263 .from_to_page(None)
264 .build()
265 .unwrap();
266 let cli_args = ListRemoteCliArgs::builder().build().unwrap();
267 let mut writer = Vec::new();
268 list_releases(remote, body_args, cli_args, &mut writer).unwrap();
269 assert_eq!("No resources found.\n", String::from_utf8(writer).unwrap());
270 }
271
272 #[test]
273 fn test_list_releases_empty_with_flush_no_warn_message() {
274 let remote = Arc::new(MockDeploy::new(true));
275 let body_args = ReleaseBodyArgs::builder()
276 .from_to_page(None)
277 .build()
278 .unwrap();
279 let cli_args = ListRemoteCliArgs::builder().flush(true).build().unwrap();
280 let mut writer = Vec::new();
281 list_releases(remote, body_args, cli_args, &mut writer).unwrap();
282 assert_eq!("", String::from_utf8(writer).unwrap());
283 }
284
285 impl DeployAsset for MockDeploy {
286 fn list(&self, _args: ReleaseAssetListBodyArgs) -> Result<Vec<ReleaseAssetMetadata>> {
287 let asset = ReleaseAssetMetadata::builder().
288 id("155582366".to_string()).
289 name("gr-x86_64-unknown-linux-musl.tar.gz".to_string()).
290 url("https://github.com/jordilin/gitar/releases/download/v0.1.28/gr-x86_64-unknown-linux-musl.tar.gz".to_string()).
291 size("2871690".to_string())
292 .created_at("2024-03-08T08:29:47Z".to_string())
293 .updated_at("2024-03-08T08:29:47Z".to_string()).build().unwrap();
294 Ok(vec![asset])
295 }
296
297 fn num_pages(&self, _args: ReleaseAssetListBodyArgs) -> Result<Option<u32>> {
298 todo!()
299 }
300
301 fn num_resources(&self, _args: ReleaseAssetListBodyArgs) -> Result<Option<NumberDeltaErr>> {
302 todo!()
303 }
304 }
305
306 #[test]
307 fn test_list_release_assets() {
308 let remote = Arc::new(MockDeploy::new(false));
309 let id = "155582366".to_string();
310 let body_args = ReleaseAssetListBodyArgs::builder()
311 .id(id.clone())
312 .list_args(Some(ListBodyArgs::builder().build().unwrap()))
313 .build()
314 .unwrap();
315 let cli_args = ReleaseAssetListCliArgs::builder()
316 .id(id)
317 .list_args(ListRemoteCliArgs::builder().build().unwrap())
318 .build()
319 .unwrap();
320 let mut writer = Vec::new();
321 list_release_assets(remote, body_args, cli_args, &mut writer).unwrap();
322 assert_eq!(
323 "ID|Name|URL|Size|Created At|Updated At\n155582366|gr-x86_64-unknown-linux-musl.tar.gz|https://github.com/jordilin/gitar/releases/download/v0.1.28/gr-x86_64-unknown-linux-musl.tar.gz|2871690|2024-03-08T08:29:47Z|2024-03-08T08:29:47Z\n", String::from_utf8(writer).unwrap());
324 }
325}