From 8a9c58e515b9dbd157ddde2009a3ef8bd379228d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 13 Aug 2024 11:12:10 -0400 Subject: [PATCH] zed_extension_api: Add `HttpRequestBuilder` (#16165) This PR adds an `HttpRequestBuilder` to the extension API to allow for a more ergonomic way for constructing HTTP requests within extensions. The HTTP client functionality is now also exposed via the `zed_extension_api::http_client` module instead of top-level. Release Notes: - N/A --- crates/extension_api/src/extension_api.rs | 6 +- crates/extension_api/src/http_client.rs | 95 +++++++++++++++++++++++ crates/extension_api/src/settings.rs | 2 + extensions/gleam/src/gleam.rs | 25 +++--- extensions/gleam/src/hexdocs.rs | 31 ++++---- 5 files changed, 125 insertions(+), 34 deletions(-) create mode 100644 crates/extension_api/src/http_client.rs diff --git a/crates/extension_api/src/extension_api.rs b/crates/extension_api/src/extension_api.rs index 4bc5889122..7b872e4414 100644 --- a/crates/extension_api/src/extension_api.rs +++ b/crates/extension_api/src/extension_api.rs @@ -1,6 +1,6 @@ //! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust. -/// Provides access to Zed settings. +pub mod http_client; pub mod settings; use core::fmt; @@ -19,10 +19,6 @@ pub use wit::{ github_release_by_tag_name, latest_github_release, GithubRelease, GithubReleaseAsset, GithubReleaseOptions, }, - zed::extension::http_client::{ - fetch, fetch_stream, HttpMethod, HttpRequest, HttpResponse, HttpResponseStream, - RedirectPolicy, - }, zed::extension::nodejs::{ node_binary_path, npm_install_package, npm_package_installed_version, npm_package_latest_version, diff --git a/crates/extension_api/src/http_client.rs b/crates/extension_api/src/http_client.rs new file mode 100644 index 0000000000..366b4e8f46 --- /dev/null +++ b/crates/extension_api/src/http_client.rs @@ -0,0 +1,95 @@ +//! An HTTP client. + +pub use crate::wit::zed::extension::http_client::{ + fetch, fetch_stream, HttpMethod, HttpRequest, HttpResponse, HttpResponseStream, RedirectPolicy, +}; + +impl HttpRequest { + /// Returns a builder for an [`HttpRequest`]. + pub fn builder() -> HttpRequestBuilder { + HttpRequestBuilder::new() + } + + /// Executes the [`HttpRequest`] with [`fetch`]. + pub fn fetch(&self) -> Result { + fetch(self) + } + + /// Executes the [`HttpRequest`] with [`fetch_stream`]. + pub fn fetch_stream(&self) -> Result { + fetch_stream(&self) + } +} + +/// A builder for an [`HttpRequest`]. +#[derive(Clone)] +pub struct HttpRequestBuilder { + method: Option, + url: Option, + headers: Vec<(String, String)>, + body: Option>, + redirect_policy: RedirectPolicy, +} + +impl HttpRequestBuilder { + /// Returns a new [`HttpRequestBuilder`]. + pub fn new() -> Self { + HttpRequestBuilder { + method: None, + url: None, + headers: Vec::new(), + body: None, + redirect_policy: RedirectPolicy::NoFollow, + } + } + + /// Sets the HTTP method for the request. + pub fn method(mut self, method: HttpMethod) -> Self { + self.method = Some(method); + self + } + + /// Sets the URL for the request. + pub fn url(mut self, url: impl Into) -> Self { + self.url = Some(url.into()); + self + } + + /// Adds a header to the request. + pub fn header(mut self, name: impl Into, value: impl Into) -> Self { + self.headers.push((name.into(), value.into())); + self + } + + /// Adds the specified headers to the request. + pub fn headers(mut self, headers: impl IntoIterator) -> Self { + self.headers.extend(headers); + self + } + + /// Sets the body of the request. + pub fn body(mut self, body: impl Into>) -> Self { + self.body = Some(body.into()); + self + } + + /// Sets the redirect policy for the request. + pub fn redirect_policy(mut self, policy: RedirectPolicy) -> Self { + self.redirect_policy = policy; + self + } + + /// Builds the [`HttpRequest`]. + pub fn build(self) -> Result { + let method = self.method.ok_or_else(|| "Method not set".to_string())?; + let url = self.url.ok_or_else(|| "URL not set".to_string())?; + + Ok(HttpRequest { + method, + url, + headers: self.headers, + body: self.body, + redirect_policy: self.redirect_policy, + }) + } +} diff --git a/crates/extension_api/src/settings.rs b/crates/extension_api/src/settings.rs index 9dfb120adb..8806806aca 100644 --- a/crates/extension_api/src/settings.rs +++ b/crates/extension_api/src/settings.rs @@ -1,3 +1,5 @@ +//! Provides access to Zed settings. + #[path = "../wit/since_v0.1.0/settings.rs"] mod types; diff --git a/extensions/gleam/src/gleam.rs b/extensions/gleam/src/gleam.rs index 975ce4e8e1..9acd808d17 100644 --- a/extensions/gleam/src/gleam.rs +++ b/extensions/gleam/src/gleam.rs @@ -1,11 +1,11 @@ mod hexdocs; use std::{fs, io}; +use zed::http_client::{HttpMethod, HttpRequest, RedirectPolicy}; use zed::lsp::CompletionKind; use zed::{ - CodeLabel, CodeLabelSpan, HttpMethod, HttpRequest, KeyValueStore, LanguageServerId, - RedirectPolicy, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, - SlashCommandOutputSection, + CodeLabel, CodeLabelSpan, KeyValueStore, LanguageServerId, SlashCommand, + SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection, }; use zed_extension_api::{self as zed, Result}; @@ -194,23 +194,20 @@ impl zed::Extension for GleamExtension { .ok_or_else(|| "missing package name".to_string())?; let module_path = components.map(ToString::to_string).collect::>(); - let response = zed::fetch(&HttpRequest { - method: HttpMethod::Get, - url: format!( + let response = HttpRequest::builder() + .method(HttpMethod::Get) + .url(format!( "https://hexdocs.pm/{package_name}{maybe_path}", maybe_path = if !module_path.is_empty() { format!("/{}.html", module_path.join("/")) } else { String::new() } - ), - headers: vec![( - "User-Agent".to_string(), - "Zed (Gleam Extension)".to_string(), - )], - body: None, - redirect_policy: RedirectPolicy::FollowAll, - })?; + )) + .header("User-Agent", "Zed (Gleam Extension)") + .redirect_policy(RedirectPolicy::FollowAll) + .build()? + .fetch()?; let (markdown, _modules) = convert_hexdocs_to_markdown(&mut io::Cursor::new(response.body))?; diff --git a/extensions/gleam/src/hexdocs.rs b/extensions/gleam/src/hexdocs.rs index 8310899f03..2b19038b30 100644 --- a/extensions/gleam/src/hexdocs.rs +++ b/extensions/gleam/src/hexdocs.rs @@ -11,7 +11,8 @@ use html_to_markdown::{ StartTagOutcome, TagHandler, }; use zed_extension_api::{ - self as zed, HttpMethod, HttpRequest, KeyValueStore, RedirectPolicy, Result, + http_client::{HttpMethod, HttpRequest, RedirectPolicy}, + KeyValueStore, Result, }; pub fn index(package: String, database: &KeyValueStore) -> Result<()> { @@ -20,13 +21,13 @@ pub fn index(package: String, database: &KeyValueStore) -> Result<()> { "Zed (Gleam Extension)".to_string(), )]; - let response = zed::fetch(&HttpRequest { - method: HttpMethod::Get, - url: format!("https://hexdocs.pm/{package}"), - headers: headers.clone(), - body: None, - redirect_policy: RedirectPolicy::FollowAll, - })?; + let response = HttpRequest::builder() + .method(HttpMethod::Get) + .url(format!("https://hexdocs.pm/{package}")) + .headers(headers.clone()) + .redirect_policy(RedirectPolicy::FollowAll) + .build()? + .fetch()?; let (package_root_markdown, modules) = convert_hexdocs_to_markdown(&mut io::Cursor::new(&response.body))?; @@ -34,13 +35,13 @@ pub fn index(package: String, database: &KeyValueStore) -> Result<()> { database.insert(&package, &package_root_markdown)?; for module in modules { - let response = zed::fetch(&HttpRequest { - method: HttpMethod::Get, - url: format!("https://hexdocs.pm/{package}/{module}.html"), - headers: headers.clone(), - body: None, - redirect_policy: RedirectPolicy::FollowAll, - })?; + let response = HttpRequest::builder() + .method(HttpMethod::Get) + .url(format!("https://hexdocs.pm/{package}/{module}.html")) + .headers(headers.clone()) + .redirect_policy(RedirectPolicy::FollowAll) + .build()? + .fetch()?; let (markdown, _modules) = convert_hexdocs_to_markdown(&mut io::Cursor::new(&response.body))?;