gr/cmds/
release.rs

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    // It can be a release tag (Gitlab) or an actual release id (Github)
82    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}