mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Add client networking stack
This commit is contained in:
parent
1a2f35801b
commit
610fd9994f
3 changed files with 98 additions and 45 deletions
|
@ -1,62 +1,23 @@
|
||||||
use crate::PypiClient;
|
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
|
||||||
use thiserror::Error;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
use crate::error::PypiClientError;
|
||||||
pub enum PypiClientError {
|
use crate::PypiClient;
|
||||||
/// 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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PypiClient {
|
impl PypiClient {
|
||||||
pub async fn simple(
|
pub async fn simple(
|
||||||
&self,
|
&self,
|
||||||
package_name: impl AsRef<str>,
|
package_name: impl AsRef<str>,
|
||||||
) -> Result<PackageDocument, PypiClientError> {
|
) -> Result<SimpleJson, PypiClientError> {
|
||||||
// Format the URL for PyPI.
|
// 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"));
|
url.set_query(Some("format=application/vnd.pypi.simple.v1+json"));
|
||||||
|
|
||||||
// Fetch from the registry.
|
// Fetch from the registry.
|
||||||
let text = self.simple_impl(package_name, &url).await?;
|
let text = self.simple_impl(package_name, &url).await?;
|
||||||
|
|
||||||
// Parse.
|
|
||||||
serde_json::from_str(&text)
|
serde_json::from_str(&text)
|
||||||
.map_err(move |e| PypiClientError::from_json_err(e, url.to_string()))
|
.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<File>,
|
||||||
|
meta: Meta,
|
||||||
|
name: String,
|
||||||
|
versions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<String>,
|
||||||
|
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.
|
/// 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
|
/// In npm, this is referred to as a "packument", which is a portmanteau of "package" and
|
||||||
|
|
41
crates/puffin-client/src/error.rs
Normal file
41
crates/puffin-client/src/error.rs
Normal file
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ use reqwest_retry::RetryTransientMiddleware;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
|
mod error;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
println!("Hello, world!");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue