http_client: Replace `build_tarball_url` with a more extensible function (#15332)

Marshall Bowers created

This PR replaces the `build_tarball_url` with `build_asset_url` that
accepts an `AssetKind` enum to support downloading different kinds of
assets from GitHub.

Right now the only asset kind we support is still `.tar.gz`, but the new
structure is more amenable to adding more asset kinds.

Release Notes:

- N/A

Change summary

crates/http_client/src/github.rs   | 25 +++++++++++++++++--------
crates/languages/src/typescript.rs | 19 ++++++++++++++-----
2 files changed, 31 insertions(+), 13 deletions(-)

Detailed changes

crates/http_client/src/github.rs 🔗

@@ -117,32 +117,41 @@ pub async fn get_release_by_tag_name(
     Ok(release)
 }
 
-pub fn build_tarball_url(repo_name_with_owner: &str, tag: &str) -> Result<String> {
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum AssetKind {
+    TarGz,
+}
+
+pub fn build_asset_url(repo_name_with_owner: &str, tag: &str, kind: AssetKind) -> Result<String> {
     let mut url = Url::parse(&format!(
         "https://github.com/{repo_name_with_owner}/archive/refs/tags",
     ))?;
     // We're pushing this here, because tags may contain `/` and other characters
     // that need to be escaped.
-    let tarball_filename = format!("{}.tar.gz", tag);
+    let asset_filename = format!(
+        "{tag}.{extension}",
+        extension = match kind {
+            AssetKind::TarGz => "tar.gz",
+        }
+    );
     url.path_segments_mut()
         .map_err(|_| anyhow!("cannot modify url path segments"))?
-        .push(&tarball_filename);
+        .push(&asset_filename);
     Ok(url.to_string())
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::github::build_tarball_url;
+    use crate::github::{build_asset_url, AssetKind};
 
     #[test]
-    fn test_build_tarball_url() {
+    fn test_build_asset_url() {
         let tag = "release/2.3.5";
         let repo_name_with_owner = "microsoft/vscode-eslint";
 
-        let have = build_tarball_url(repo_name_with_owner, tag).unwrap();
-
+        let tarball = build_asset_url(repo_name_with_owner, tag, AssetKind::TarGz).unwrap();
         assert_eq!(
-            have,
+            tarball,
             "https://github.com/microsoft/vscode-eslint/archive/refs/tags/release%2F2.3.5.tar.gz"
         );
     }

crates/languages/src/typescript.rs 🔗

@@ -4,7 +4,7 @@ use async_tar::Archive;
 use async_trait::async_trait;
 use collections::HashMap;
 use gpui::AsyncAppContext;
-use http_client::github::{build_tarball_url, GitHubLspBinaryVersion};
+use http_client::github::{build_asset_url, AssetKind, GitHubLspBinaryVersion};
 use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
 use lsp::{CodeActionKind, LanguageServerBinary};
 use node_runtime::NodeRuntime;
@@ -296,6 +296,7 @@ pub struct EsLintLspAdapter {
 
 impl EsLintLspAdapter {
     const CURRENT_VERSION: &'static str = "release/2.4.4";
+    const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;
 
     const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
     const SERVER_NAME: &'static str = "eslint";
@@ -406,7 +407,11 @@ impl LspAdapter for EsLintLspAdapter {
         &self,
         _delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let url = build_tarball_url("microsoft/vscode-eslint", Self::CURRENT_VERSION)?;
+        let url = build_asset_url(
+            "microsoft/vscode-eslint",
+            Self::CURRENT_VERSION,
+            Self::GITHUB_ASSET_KIND,
+        )?;
 
         Ok(Box::new(GitHubLspBinaryVersion {
             name: Self::CURRENT_VERSION.into(),
@@ -432,9 +437,13 @@ impl LspAdapter for EsLintLspAdapter {
                 .get(&version.url, Default::default(), true)
                 .await
                 .map_err(|err| anyhow!("error downloading release: {}", err))?;
-            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
-            let archive = Archive::new(decompressed_bytes);
-            archive.unpack(&destination_path).await?;
+            match Self::GITHUB_ASSET_KIND {
+                AssetKind::TarGz => {
+                    let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
+                    let archive = Archive::new(decompressed_bytes);
+                    archive.unpack(&destination_path).await?;
+                }
+            }
 
             let mut dir = fs::read_dir(&destination_path).await?;
             let first = dir.next().await.ok_or(anyhow!("missing first file"))??;