From d35af4a51f39fccfd6f95896fa5fcc9f59b7b766 Mon Sep 17 00:00:00 2001 From: Manu Bretelle Date: Thu, 9 Mar 2023 19:05:06 -0800 Subject: [PATCH] [github] method to download URLs from github structs such as [Artifact[(https://docs.rs/octorust/latest/octorust/types/struct.Artifact.html) have a field where we can download the artifact. This download MUST be authenticated or one would get the following error: ``` { "message": "You must have the actions scope to download artifacts.", "documentation_url": "https://docs.github.com/rest/reference/actions#download-an-artifact" } ``` The `Client` already has all the details and logic needed for performing an authenticated request. It would be useful to re-use this and expose it to the application using the crate in some ways. This change adds a naive `get_url` method that given a URL, will download the content and return the raw bytes. --- github/Cargo.toml | 1 + github/src/lib.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/github/Cargo.toml b/github/Cargo.toml index 752fc762..fc2d7ed1 100644 --- a/github/Cargo.toml +++ b/github/Cargo.toml @@ -18,6 +18,7 @@ rustls-tls = ["reqwest/rustls-tls", "ring", "pem"] [dependencies] anyhow = "1" async-recursion = "^1.0" +bytes = "1" chrono = { version = "0.4", default-features = false, features = ["serde"] } dirs = { version = "^3.0.2", optional = true } http = "^0.2.4" diff --git a/github/src/lib.rs b/github/src/lib.rs index 29540df7..6ff9ef2d 100644 --- a/github/src/lib.rs +++ b/github/src/lib.rs @@ -631,6 +631,67 @@ impl Client { } } + pub async fn get_url(&self, uri: &str) -> Result { + let authentication = crate::auth::AuthenticationConstraint::Unconstrained; + + let (url, auth) = self.url_and_auth(uri, authentication).await?; + + let instance = <&Client>::clone(&self); + + let mut req = instance.client.request(http::Method::GET, url); + + req = req.header(http::header::USER_AGENT, &*instance.agent); + + if let Some(auth_str) = auth { + req = req.header(http::header::AUTHORIZATION, &*auth_str); + } + + let response = req.send().await?; + + #[cfg(not(feature = "httpcache"))] + let (remaining, reset) = crate::utils::get_header_values(response.headers()); + + #[cfg(feature = "httpcache")] + let (remaining, reset, etag) = crate::utils::get_header_values(response.headers()); + + let status = response.status(); + /* + let link = response + .headers() + .get(http::header::LINK) + .and_then(|l| l.to_str().ok()) + .and_then(|l| l.parse().ok()); + */ + let response_body = response.bytes().await?; + + if status.is_success() { + log::debug!("Received successful response. Read payload."); + + Ok(response_body) + } else { + let error = match (remaining, reset) { + (Some(remaining), Some(reset)) if remaining == 0 => { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + anyhow!( + "rate limit exceeded, will reset in {} seconds", + u64::from(reset) - now + ) + } + _ => { + if response_body.is_empty() { + anyhow!("code: {}, empty response", status) + } else { + anyhow!("code: {}, error: {:?}", status, response_body,) + } + } + }; + Err(error) + } + } + async fn request_entity( &self, method: http::Method,