Enhance GitHub API error handling (#496)

* feat: Enhance GitHub API error handling

* docs: Update changelog
This commit is contained in:
Sergio Gasquez Arcos
2025-05-19 09:49:09 +02:00
committed by GitHub
parent 6d6e3d08d6
commit 4efd93c6a7
4 changed files with 56 additions and 19 deletions

View File

@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Changed
- Improved GitHub API error handling (#496)
- update `zip` dependency to 3.0.0
## [0.15.0] - 2025-04-08

View File

@@ -14,6 +14,10 @@ pub enum Error {
#[error("Failed to query GitHub API: Invalid Github token")]
GithubTokenInvalid,
#[diagnostic(code(espup::toolchain::rust::query_github))]
#[error("Failed to connect to GitHub API: {0}")]
GithubConnectivityError(String),
#[diagnostic(code(espup::toolchain::http_error))]
#[error("HTTP GET Error: {0}")]
HttpError(String),

View File

@@ -237,10 +237,11 @@ pub async fn install(args: InstallOpts, install_mode: InstallMode) -> Result<()>
toolchain_version.clone()
}
} else {
// Get the latest version of the Xtensa Rust toolchain. If that fails, return an error::GithubTokenInvalid
XtensaRust::get_latest_version()
.await
.map_err(|_| Error::GithubTokenInvalid)?
// Get the latest version of the Xtensa Rust toolchain
XtensaRust::get_latest_version().await.map_err(|e| {
warn!("Failed to get latest Xtensa Rust version: {}", e);
e
})?
};
let toolchain_dir = get_rustup_home().join("toolchains").join(args.name);
let llvm: Llvm = Llvm::new(
@@ -382,27 +383,42 @@ pub fn github_query(url: &str) -> Result<serde_json::Value, Error> {
.unwrap(),
);
}
let client = build_proxy_blocking_client()?;
let json: Result<serde_json::Value, Error> = retry(
Fixed::from_millis(100).take(5),
|| -> Result<serde_json::Value, Error> {
let res = client.get(url).headers(headers.clone()).send()?.text()?;
let response = client.get(url).headers(headers.clone()).send()?;
let status = response.status();
if !status.is_success() {
return Err(Error::HttpError(format!(
"GitHub API returned status code: {}",
status
)));
}
let res = response.text()?;
// Check for rate limiting response
if res.contains(
"https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting",
) {
return Err(Error::GithubRateLimit);
}
// Check for authentication errors
if res.contains("Bad credentials") {
return Err(Error::GithubTokenInvalid);
}
let json: serde_json::Value =
serde_json::from_str(&res).map_err(|_| Error::SerializeJson)?;
Ok(json)
// Try to parse the JSON
serde_json::from_str(&res).map_err(|_| Error::SerializeJson)
},
)
.map_err(|err| err.error);
json
}

View File

@@ -72,19 +72,35 @@ pub struct XtensaRust {
impl XtensaRust {
/// Get the latest version of Xtensa Rust toolchain.
pub async fn get_latest_version() -> Result<String> {
let json = tokio::task::spawn_blocking(|| github_query(XTENSA_RUST_LATEST_API_URL))
.await
.unwrap()?;
let mut version = json["tag_name"].to_string();
pub async fn get_latest_version() -> Result<String, Error> {
debug!("Querying latest Xtensa Rust version from GitHub API");
version.retain(|c| c != 'v' && c != '"');
let borrowed = version.clone();
tokio::task::spawn_blocking(move || Self::parse_version(&borrowed))
// First, handle the spawn_blocking result
let query_result = tokio::task::spawn_blocking(|| github_query(XTENSA_RUST_LATEST_API_URL))
.await
.expect("Join blocking task error")?;
debug!("Latest Xtensa Rust version: {}", version);
Ok(version)
.map_err(|e| {
Error::GithubConnectivityError(format!("Failed to query GitHub API: {}", e))
})?;
// Then handle the github_query result
let json = query_result?;
if !json.is_object() || !json["tag_name"].is_string() {
return Err(Error::SerializeJson);
}
let mut version = json["tag_name"].to_string();
version.retain(|c| c != 'v' && c != '"');
// Validate the version format - handle both spawning and parsing errors
let parse_task = tokio::task::spawn_blocking(move || Self::parse_version(&version))
.await
.map_err(|_| Error::SerializeJson)?;
let validated_version = parse_task?;
debug!("Latest Xtensa Rust version: {}", validated_version);
Ok(validated_version)
}
/// Create a new instance.