From 610fd9994fddaf5e3bd03763e78d4051a3936a3d Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 4 Oct 2023 19:43:15 -0400 Subject: [PATCH] Add client networking stack --- crates/puffin-client/src/api/mod.rs | 101 +++++++++++++++------------- crates/puffin-client/src/error.rs | 41 +++++++++++ crates/puffin-client/src/lib.rs | 1 + 3 files changed, 98 insertions(+), 45 deletions(-) create mode 100644 crates/puffin-client/src/error.rs diff --git a/crates/puffin-client/src/api/mod.rs b/crates/puffin-client/src/api/mod.rs index 7870a5b04..675984444 100644 --- a/crates/puffin-client/src/api/mod.rs +++ b/crates/puffin-client/src/api/mod.rs @@ -1,62 +1,23 @@ -use crate::PypiClient; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use thiserror::Error; use url::Url; -#[derive(Debug, Error)] -pub enum PypiClientError { - /// An invalid URL was provided. - #[error(transparent)] - UrlParseError(#[from] url::ParseError), - - /// The package was not found in the registry. - /// - /// Make sure the package name is spelled correctly and that you've - /// configured the right registry to fetch it from. - #[error("Package `{1}` was not found in registry {0}.")] - PackageNotFound(Url, String), - - /// A generic request error happened while making a request. Refer to the - /// error message for more details. - #[error(transparent)] - RequestError(#[from] reqwest::Error), - - /// A generic request middleware error happened while making a request. - /// Refer to the error message for more details. - #[error(transparent)] - RequestMiddlewareError(#[from] reqwest_middleware::Error), - - #[error("Received some unexpected JSON. Unable to parse.")] - BadJson { - source: serde_json::Error, - url: String, - }, -} - -impl PypiClientError { - pub fn from_json_err(err: serde_json::Error, url: String) -> Self { - Self::BadJson { - source: err, - url: url.clone(), - } - } -} +use crate::error::PypiClientError; +use crate::PypiClient; impl PypiClient { pub async fn simple( &self, package_name: impl AsRef, - ) -> Result { + ) -> Result { // Format the URL for PyPI. - let mut url = self.registry.join("simple")?.join(package_name.as_ref())?; + let mut url = self.registry.join("simple")?; + url.path_segments_mut().unwrap().push(package_name.as_ref()); + url.path_segments_mut().unwrap().push(""); url.set_query(Some("format=application/vnd.pypi.simple.v1+json")); // Fetch from the registry. let text = self.simple_impl(package_name, &url).await?; - - // Parse. serde_json::from_str(&text) .map_err(move |e| PypiClientError::from_json_err(e, url.to_string())) } @@ -87,6 +48,56 @@ impl PypiClient { } } +#[derive(Debug, Serialize, Deserialize)] +pub struct SimpleJson { + files: Vec, + meta: Meta, + name: String, + versions: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct File { + core_metadata: Metadata, + data_dist_info_metadata: Metadata, + filename: String, + hashes: Hashes, + requires_python: Option, + size: i64, + upload_time: String, + url: String, + yanked: Yanked, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Metadata { + Bool(bool), + Hashes(Hashes), +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Yanked { + Bool(bool), + Reason(String), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Hashes { + sha256: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Meta { + #[serde(rename = "_last-serial")] + last_serial: i64, + api_version: String, +} + + /// The metadata for a single package, including the pubishing versions and their artifacts. /// /// In npm, this is referred to as a "packument", which is a portmanteau of "package" and diff --git a/crates/puffin-client/src/error.rs b/crates/puffin-client/src/error.rs new file mode 100644 index 000000000..48ccae958 --- /dev/null +++ b/crates/puffin-client/src/error.rs @@ -0,0 +1,41 @@ +use thiserror::Error; +use url::Url; + +#[derive(Debug, Error)] +pub enum PypiClientError { + /// An invalid URL was provided. + #[error(transparent)] + UrlParseError(#[from] url::ParseError), + + /// The package was not found in the registry. + /// + /// Make sure the package name is spelled correctly and that you've + /// configured the right registry to fetch it from. + #[error("Package `{1}` was not found in registry {0}.")] + PackageNotFound(Url, String), + + /// A generic request error happened while making a request. Refer to the + /// error message for more details. + #[error(transparent)] + RequestError(#[from] reqwest::Error), + + /// A generic request middleware error happened while making a request. + /// Refer to the error message for more details. + #[error(transparent)] + RequestMiddlewareError(#[from] reqwest_middleware::Error), + + #[error("Received some unexpected JSON. Unable to parse.")] + BadJson { + source: serde_json::Error, + url: String, + }, +} + +impl PypiClientError { + pub fn from_json_err(err: serde_json::Error, url: String) -> Self { + Self::BadJson { + source: err, + url: url.clone(), + } + } +} diff --git a/crates/puffin-client/src/lib.rs b/crates/puffin-client/src/lib.rs index c9b3b8a54..6456997d2 100644 --- a/crates/puffin-client/src/lib.rs +++ b/crates/puffin-client/src/lib.rs @@ -9,6 +9,7 @@ use reqwest_retry::RetryTransientMiddleware; use url::Url; mod api; +mod error; fn main() { println!("Hello, world!");