gr/gitlab/
release.rs

1use crate::{
2    api_traits::{ApiOperation, Deploy, DeployAsset, NumberDeltaErr},
3    cmds::release::{Release, ReleaseAssetListBodyArgs, ReleaseAssetMetadata, ReleaseBodyArgs},
4    http,
5    io::{HttpResponse, HttpRunner},
6    remote::query,
7    Result,
8};
9
10use super::Gitlab;
11
12impl<R: HttpRunner<Response = HttpResponse>> Deploy for Gitlab<R> {
13    fn list(&self, args: ReleaseBodyArgs) -> Result<Vec<Release>> {
14        let url = format!("{}/releases", self.rest_api_basepath());
15        query::paged(
16            &self.runner,
17            &url,
18            args.from_to_page,
19            self.headers(),
20            None,
21            ApiOperation::Release,
22            |value| GitlabReleaseFields::from(value).into(),
23        )
24    }
25
26    fn num_pages(&self) -> Result<Option<u32>> {
27        let (url, headers) = self.resource_release_metadata_url();
28        query::num_pages(&self.runner, &url, headers, ApiOperation::Release)
29    }
30
31    fn num_resources(&self) -> Result<Option<NumberDeltaErr>> {
32        let (url, headers) = self.resource_release_metadata_url();
33        query::num_resources(&self.runner, &url, headers, ApiOperation::Release)
34    }
35}
36
37impl<R: HttpRunner<Response = HttpResponse>> Gitlab<R> {
38    fn resource_release_metadata_url(&self) -> (String, http::Headers) {
39        let url = format!("{}/releases?page=1", self.rest_api_basepath());
40        let headers = self.headers();
41        (url, headers)
42    }
43
44    fn get_release(&self, args: ReleaseAssetListBodyArgs) -> Result<serde_json::Value> {
45        let url = format!("{}/releases/{}", self.rest_api_basepath(), args.id);
46        query::get_json::<_, ()>(
47            &self.runner,
48            &url,
49            None,
50            self.headers(),
51            ApiOperation::Release,
52        )
53    }
54}
55
56impl<R: HttpRunner<Response = HttpResponse>> DeployAsset for Gitlab<R> {
57    fn list(&self, args: ReleaseAssetListBodyArgs) -> Result<Vec<ReleaseAssetMetadata>> {
58        let release = self.get_release(args)?;
59        let mut asset_metadata = Vec::new();
60        build_release_assets(&release, &mut asset_metadata, AssetType::Sources);
61        // _links is considered an asset in the Gitlab API, include those too.
62        build_release_assets(&release, &mut asset_metadata, AssetType::Links);
63        Ok(asset_metadata)
64    }
65
66    fn num_pages(&self, args: ReleaseAssetListBodyArgs) -> Result<Option<u32>> {
67        let url = format!("{}/releases/{}?page=1", self.rest_api_basepath(), args.id);
68        // Assets is a one single request to the release API endpoint for
69        // Gitlab, so there's only one page available. If the HEAD request
70        // succeeds, then set it to one.
71        query::num_pages(&self.runner, &url, self.headers(), ApiOperation::Release)?;
72        Ok(Some(1))
73    }
74
75    fn num_resources(&self, args: ReleaseAssetListBodyArgs) -> Result<Option<NumberDeltaErr>> {
76        // Number of resources comes by doing a GET request to the release API
77        // See JSON doc contracts/gitlab/list_release_assets.json where the
78        // number or resources is in the field assets.count
79        let release = self.get_release(args)?;
80        let count = release["assets"]["count"].as_u64().unwrap();
81        Ok(Some(NumberDeltaErr::new(1, count as u32)))
82    }
83}
84
85enum AssetType {
86    Sources,
87    Links,
88}
89
90impl AsRef<str> for AssetType {
91    fn as_ref(&self) -> &str {
92        match self {
93            AssetType::Sources => "sources",
94            AssetType::Links => "links",
95        }
96    }
97}
98
99fn build_release_assets(
100    release: &serde_json::Value,
101    asset_metadata: &mut Vec<ReleaseAssetMetadata>,
102    asset_type: AssetType,
103) {
104    let assets = release["assets"][asset_type.as_ref()].as_array().unwrap();
105    for asset in assets {
106        let asset_data = ReleaseAssetMetadata::builder()
107            // There's no id available in the response per se. Grab the short commit
108            // id instead
109            .id(release["commit"]["short_id"].as_str().unwrap().to_string())
110            .name(release["name"].as_str().unwrap().to_string())
111            .url(asset["url"].as_str().unwrap().to_string())
112            .size("".to_string())
113            .created_at(release["created_at"].as_str().unwrap().to_string())
114            .updated_at(release["released_at"].as_str().unwrap().to_string())
115            .build()
116            .unwrap();
117        asset_metadata.push(asset_data);
118    }
119}
120
121pub struct GitlabReleaseFields {
122    release: Release,
123}
124
125impl From<&serde_json::Value> for GitlabReleaseFields {
126    fn from(value: &serde_json::Value) -> Self {
127        Self {
128            release: Release::builder()
129                // There's no id available in the response per se. Grab the short commit
130                // id instead
131                .id(value["commit"]["short_id"].as_str().unwrap().to_string())
132                .url(value["_links"]["self"].as_str().unwrap().to_string())
133                .tag(value["tag_name"].as_str().unwrap().to_string())
134                .title(value["name"].as_str().unwrap().to_string())
135                .description(value["description"].as_str().unwrap().to_string())
136                .prerelease(value["upcoming_release"].as_bool().unwrap())
137                .created_at(value["created_at"].as_str().unwrap().to_string())
138                .updated_at(value["released_at"].as_str().unwrap().to_string())
139                .build()
140                .unwrap(),
141        }
142    }
143}
144
145impl From<GitlabReleaseFields> for Release {
146    fn from(fields: GitlabReleaseFields) -> Self {
147        fields.release
148    }
149}
150
151#[cfg(test)]
152mod test {
153
154    use crate::{
155        http::Headers,
156        setup_client,
157        test::utils::{default_gitlab, ContractType, ResponseContracts},
158    };
159
160    use super::*;
161
162    #[test]
163    fn test_list_release() {
164        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
165            200,
166            "list_releases.json",
167            None,
168        );
169        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn Deploy);
170        let args = ReleaseBodyArgs::builder()
171            .from_to_page(None)
172            .build()
173            .unwrap();
174        let releases = gitlab.list(args).unwrap();
175        assert_eq!(
176            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/releases",
177            *client.url(),
178        );
179        assert_eq!(Some(ApiOperation::Release), *client.api_operation.borrow());
180        assert_eq!(1, releases.len());
181    }
182
183    #[test]
184    fn test_release_num_pages() {
185        let link_header = "<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/releases?page=1>; rel=\"first\", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/releases?page=1>; rel=\"last\"";
186        let mut headers = Headers::new();
187        headers.set("link", link_header);
188        let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
189            200,
190            None,
191            Some(headers),
192        );
193        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn Deploy);
194        let num_pages = gitlab.num_pages().unwrap();
195        assert_eq!(
196            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/releases?page=1",
197            *client.url(),
198        );
199        assert_eq!(Some(ApiOperation::Release), *client.api_operation.borrow());
200        assert_eq!(Some(1), num_pages);
201    }
202
203    #[test]
204    fn test_list_release_assets() {
205        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
206            200,
207            "list_release_assets.json",
208            None,
209        );
210        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn DeployAsset);
211        let args = ReleaseAssetListBodyArgs::builder()
212            .id("v0.1.18-alpha-2".to_string())
213            .list_args(None)
214            .build()
215            .unwrap();
216        let assets = gitlab.list(args).unwrap();
217        assert_eq!(
218            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/releases/v0.1.18-alpha-2",
219            *client.url(),
220        );
221        assert_eq!(Some(ApiOperation::Release), *client.api_operation.borrow());
222        assert_eq!(5, assets.len());
223    }
224
225    #[test]
226    fn test_list_release_assets_not_ok_status_code_error() {
227        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
228            404,
229            "list_release_assets.json",
230            None,
231        );
232        let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn DeployAsset);
233        let args = ReleaseAssetListBodyArgs::builder()
234            .id("v0.1.18-alpha-2".to_string())
235            .list_args(None)
236            .build()
237            .unwrap();
238        let assets = gitlab.list(args);
239        assert!(assets.is_err());
240    }
241
242    #[test]
243    fn test_list_release_assets_num_pages() {
244        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
245            200,
246            "list_release_assets.json",
247            None,
248        );
249        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn DeployAsset);
250        let args = ReleaseAssetListBodyArgs::builder()
251            .id("v0.1.18-alpha-2".to_string())
252            .list_args(None)
253            .build()
254            .unwrap();
255        let num_pages = gitlab.num_pages(args).unwrap();
256        assert_eq!(
257            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/releases/v0.1.18-alpha-2?page=1",
258            *client.url(),
259        );
260        assert_eq!(Some(ApiOperation::Release), *client.api_operation.borrow());
261        assert_eq!(Some(1), num_pages);
262    }
263
264    #[test]
265    fn test_list_release_assets_num_resources() {
266        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
267            200,
268            "list_release_assets.json",
269            None,
270        );
271        let (_, gitlab) = setup_client!(contracts, default_gitlab(), dyn DeployAsset);
272        let args = ReleaseAssetListBodyArgs::builder()
273            .id("v0.1.18-alpha-2".to_string())
274            .list_args(None)
275            .build()
276            .unwrap();
277        let num_resources = gitlab.num_resources(args).unwrap().unwrap();
278        assert_eq!("(1, 5)", &num_resources.to_string());
279    }
280}