github_workflows_update/updater/
github.rs

1// Copyright (C) 2022 Leandro Lisboa Penz <lpenz@lpenz.org>
2// This file is subject to the terms and conditions defined in
3// file 'LICENSE', which is part of this source code package.
4
5use reqwest::header::USER_AGENT;
6use tracing::instrument;
7use url::Url;
8
9use crate::error::Error;
10use crate::error::Result;
11use crate::version::Version;
12
13#[instrument(level = "debug")]
14async fn get_json(url: &Url) -> Result<serde_json::Value> {
15    let client = reqwest::Client::new();
16    let mut builder = client.get(url.as_str());
17    builder = builder.header(USER_AGENT, "reqwest");
18    builder = builder.header("Accept", "application/vnd.github.v3+json");
19    if let Ok(token) = std::env::var("PERSONAL_TOKEN") {
20        builder = builder.header("Authorization", format!("token {token}"));
21    }
22    let response = builder.send().await?;
23    if !response.status().is_success() {
24        return Err(Error::HttpError(url.clone(), response.status()));
25    }
26    Ok(response.json::<serde_json::Value>().await?)
27}
28
29#[instrument(level = "debug")]
30fn parse_versions(data: serde_json::Value) -> Result<Vec<Version>> {
31    let re_ref = regex::Regex::new(r"^refs/tags/(?P<version>.+)$").unwrap();
32    data.as_array()
33        .ok_or_else(|| Error::JsonParsing("invalid type for layer object list".into()))?
34        .iter()
35        .map(|tag_obj| {
36            tag_obj
37                .as_object()
38                .ok_or_else(|| Error::JsonParsing("invalid type for tag object".into()))?
39                .get("ref")
40                .ok_or_else(|| Error::JsonParsing("ref field not found in tag object".into()))
41                .map(|ref_value| {
42                    let version_str = ref_value.as_str().ok_or_else(|| {
43                        Error::JsonParsing("invalid type for ref field in tag object".into())
44                    })?;
45                    let m = re_ref.captures(version_str).ok_or_else(|| {
46                        Error::JsonParsing(format!(
47                            "could not match github ref {version_str} to tag regex"
48                        ))
49                    })?;
50                    let version_str = m.name("version").unwrap().as_str();
51                    Version::new(version_str)
52                        .ok_or_else(|| Error::VersionParsing(version_str.into()))
53                })?
54        })
55        .collect::<Result<Vec<Version>>>()
56}
57
58#[instrument(level = "debug")]
59pub async fn get_versions(url: &Url) -> Result<Vec<Version>> {
60    let data = get_json(url).await?;
61    let versions = parse_versions(data)?;
62    Ok(versions)
63}
64
65#[test]
66fn test_docker_parse_versions() -> Result<()> {
67    let json_str = r#"
68[
69  {
70    "ref": "refs/tags/v0.1",
71    "node_id": "REF_kwDOHcsoLq5yZWZzL3RhZ3MvdjAuMQ",
72    "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/refs/tags/v0.1",
73    "object": {
74      "sha": "ca550057e88e5885030e756b90bd040ad7840cee",
75      "type": "commit",
76      "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/commits/ca550057e88e5885030e756b90bd040ad7840cee"
77    }
78  },
79  {
80    "ref": "refs/tags/0.2",
81    "node_id": "REF_kwDOHcsoLq5yZWZzL3RhZ3MvdjAuMg",
82    "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/refs/tags/v0.2",
83    "object": {
84      "sha": "2b80e7d13e4b1738a17887b4d66143433267cea6",
85      "type": "commit",
86      "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/commits/2b80e7d13e4b1738a17887b4d66143433267cea6"
87    }
88  },
89  {
90    "ref": "refs/tags/latest",
91    "node_id": "REF_kwDOHcsoLq5yZWZzL3RhZ3MvdjAuMw",
92    "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/refs/tags/v0.3",
93    "object": {
94      "sha": "c7d367f5f10a2605aa43a540f9f88177d5fa12ac",
95      "type": "commit",
96      "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/commits/c7d367f5f10a2605aa43a540f9f88177d5fa12ac"
97    }
98  },
99  {
100    "ref": "refs/tags/v0.4",
101    "node_id": "REF_kwDOHcsoLq5yZWZzL3RhZ3MvdjAuNA",
102    "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/refs/tags/v0.4",
103    "object": {
104      "sha": "04bb04c23563d3302fe6ca0c2b832e9e67c47d58",
105      "type": "commit",
106      "url": "https://api.github.com/repos/lpenz/ghworkflow-rust/git/commits/04bb04c23563d3302fe6ca0c2b832e9e67c47d58"
107    }
108  }
109]
110"#;
111    let json_value: serde_json::Value = serde_json::from_str(json_str)?;
112    let mut versions = parse_versions(json_value)?.into_iter().collect::<Vec<_>>();
113    versions.sort();
114    let versions = versions
115        .into_iter()
116        .map(|v| format!("{}", v))
117        .collect::<Vec<_>>();
118    assert_eq!(versions, ["latest", "v0.1", "0.2", "v0.4"]);
119    Ok(())
120}