pub fn upload(
    registry: &Registry,
    wheel_path: &Path,
    metadata21: &[(String, String)],
    supported_version: &str,
) -> Result<(), UploadError> {
    let mut wheel = File::open(&wheel_path)?;
    let mut hasher = Sha256::new();
    io::copy(&mut wheel, &mut hasher)?;
    let hash_hex = format!("{:x}", hasher.finalize());

    let mut api_metadata = vec![
        (":action".to_string(), "file_upload".to_string()),
        ("sha256_digest".to_string(), hash_hex),
        ("protocol_version".to_string(), "1".to_string()),
    ];

    api_metadata.push(("pyversion".to_string(), supported_version.to_string()));

    if supported_version != "source" {
        api_metadata.push(("filetype".to_string(), "bdist_wheel".to_string()));
    } else {
        api_metadata.push(("filetype".to_string(), "sdist".to_string()));
    }

    let joined_metadata: Vec<(String, String)> = api_metadata
        .into_iter()
        // Type system shenanigans
        .chain(metadata21.to_vec().into_iter())
        // All fields must be lower case and with underscores or they will be ignored by warehouse
        .map(|(key, value)| (key.to_lowercase().replace("-", "_"), value))
        .collect();

    let mut form = Form::new();
    for (key, value) in joined_metadata {
        form = form.text(key, value.to_owned())
    }

    form = form.file("content", &wheel_path)?;

    let client = Client::new();
    let response = client
        .post(registry.url.clone())
        .header(
            reqwest::header::USER_AGENT,
            format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
        )
        .multipart(form)
        .basic_auth(registry.username.clone(), Some(registry.password.clone()))
        .send()?;

    let status = response.status();
    if status.is_success() {
        return Ok(());
    }
    let err_text = response.text().unwrap_or_else(|e| {
        format!(
            "The registry should return some text, even in case of an error, but didn't ({})",
            e
        )
    });
    // Detect FileExistsError the way twine does
    // https://github.com/pypa/twine/blob/87846e5777b380d4704704a69e1f9a7a1231451c/twine/commands/upload.py#L30
    if status == StatusCode::FORBIDDEN {
        if err_text.contains("overwrite artifact") {
            // Artifactory (https://jfrog.com/artifactory/)
            Err(UploadError::FileExistsError(err_text))
        } else {
            Err(UploadError::AuthenticationError)
        }
    } else {
        let status_string = status.to_string();
        if status == StatusCode::CONFLICT // pypiserver (https://pypi.org/project/pypiserver)
            // PyPI / TestPyPI
            || (status == StatusCode::BAD_REQUEST && err_text.contains("already exists"))
            // Nexus Repository OSS (https://www.sonatype.com/nexus-repository-oss)
            || (status == StatusCode::BAD_REQUEST && err_text.contains("updating asset"))
            // # Gitlab Enterprise Edition (https://about.gitlab.com)
            || (status == StatusCode::BAD_REQUEST && err_text.contains("already been taken"))
        {
            Err(UploadError::FileExistsError(err_text))
        } else {
            Err(UploadError::StatusCodeError(status_string, err_text))
        }
    }
}
/// Uploads a single wheel to the registry
Uploads a single wheel to the registry
Uploads a single wheel to the registry
pub fn upload(
    registry: &Registry,
    wheel_path: &Path,
    metadata21: &[(String, String)],
    supported_version: &str,
) -> Result<(), UploadError> {
    let mut wheel = File::open(&wheel_path)?;
    let mut hasher = Sha256::new();
    io::copy(&mut wheel, &mut hasher)?;
    let hash_hex = format!("{:x}", hasher.finalize());
    let mut api_metadata = vec![
        (":action".to_string(), "file_upload".to_string()),
        ("sha256_digest".to_string(), hash_hex),
        ("protocol_version".to_string(), "1".to_string()),
    ];
    api_metadata.push(("pyversion".to_string(), supported_version.to_string()));
    if supported_version != "source" {
        api_metadata.push(("filetype".to_string(), "bdist_wheel".to_string()));
    } else {
        api_metadata.push(("filetype".to_string(), "sdist".to_string()));
    }
    let joined_metadata: Vec<(String, String)> = api_metadata
        .into_iter()
        // Type system shenanigans
        .chain(metadata21.to_vec().into_iter())
        // All fields must be lower case and with underscores or they will be ignored by warehouse
        .map(|(key, value)| (key.to_lowercase().replace("-", "_"), value))
        .collect();
    let mut form = Form::new();
    for (key, value) in joined_metadata {
        form = form.text(key, value.to_owned())
    }
    form = form.file("content", &wheel_path)?;
    let client = Client::new();
    let response = client
        .post(registry.url.clone())
        .header(
            reqwest::header::USER_AGENT,
            format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
        )
        .multipart(form)
        .basic_auth(registry.username.clone(), Some(registry.password.clone()))
        .send()?;
    let status = response.status();
    if status.is_success() {
        return Ok(());
    }
    let err_text = response.text().unwrap_or_else(|e| {
        format!(
            "The registry should return some text, even in case of an error, but didn't ({})",
            e
        )
    });
    // Detect FileExistsError the way twine does
    // https://github.com/pypa/twine/blob/87846e5777b380d4704704a69e1f9a7a1231451c/twine/commands/upload.py#L30
    if status == StatusCode::FORBIDDEN {
        if err_text.contains("overwrite artifact") {
            // Artifactory (https://jfrog.com/artifactory/)
            Err(UploadError::FileExistsError(err_text))
        } else {
            Err(UploadError::AuthenticationError)
        }
    } else {
        let status_string = status.to_string();
        if status == StatusCode::CONFLICT // pypiserver (https://pypi.org/project/pypiserver)
            // PyPI / TestPyPI
            || (status == StatusCode::BAD_REQUEST && err_text.contains("already exists"))
            // Nexus Repository OSS (https://www.sonatype.com/nexus-repository-oss)
            || (status == StatusCode::BAD_REQUEST && err_text.contains("updating asset"))
            // # Gitlab Enterprise Edition (https://about.gitlab.com)
            || (status == StatusCode::BAD_REQUEST && err_text.contains("already been taken"))
        {
            Err(UploadError::FileExistsError(err_text))
        } else {
            Err(UploadError::StatusCodeError(status_string, err_text))
        }
    }
}
pub fn upload ( registry : & Registry , wheel_path : & Path , metadata21 : & [ ( String , String ) ] , supported_version : & str , ) -> Result < ( ) , UploadError > { let mut wheel = File :: open ( & wheel_path ) ? ; let mut hasher = Sha256 :: new ( ) ; io :: copy ( & mut wheel , & mut hasher ) ? ; let hash_hex = format ! ( " " hasher finalize ( ) ) ; let mut api_metadata = vec ! [ ( " " to_string ( ) " " to_string ( ) ) ( " " to_string ( ) hash_hex ) ( " " to_string ( ) " " to_string ( ) ) ] ; api_metadata . push ( ( " " . to_string ( ) , supported_version . to_string ( ) ) ) ; if supported_version != " " { api_metadata . push ( ( " " . to_string ( ) , " " . to_string ( ) ) ) ; } else { api_metadata . push ( ( " " . to_string ( ) , " " . to_string ( ) ) ) ; } let joined_metadata : Vec < ( String , String ) > = api_metadata . into_iter ( ) // Type system shenanigans . chain ( metadata21 . to_vec ( ) . into_iter ( ) ) // All fields must be lower case and with underscores or they will be ignored by warehouse . map ( | ( key , value ) | ( key . to_lowercase ( ) . replace ( " " , " " ) , value ) ) . collect ( ) ; let mut form = Form :: new ( ) ; for ( key , value ) in joined_metadata { form = form . text ( key , value . to_owned ( ) ) } form = form . file ( " " , & wheel_path ) ? ; let client = Client :: new ( ) ; let response = client . post ( registry . url . clone ( ) ) . header ( reqwest :: header :: USER_AGENT , format ! ( " " env ( " " ) env ( " " ) ) , ) . multipart ( form ) . basic_auth ( registry . username . clone ( ) , Some ( registry . password . clone ( ) ) ) . send ( ) ? ; let status = response . status ( ) ; if status . is_success ( ) { return Ok ( ( ) ) ; } let err_text = response . text ( ) . unwrap_or_else ( | e | { format ! ( " " e ) } ) ; // Detect FileExistsError the way twine does // https://github.com/pypa/twine/blob/87846e5777b380d4704704a69e1f9a7a1231451c/twine/commands/upload.py#L30 if status == StatusCode :: FORBIDDEN { if err_text . contains ( " " ) { // Artifactory (https://jfrog.com/artifactory/) Err ( UploadError :: FileExistsError ( err_text ) ) } else { Err ( UploadError :: AuthenticationError ) } } else { let status_string = status . to_string ( ) ; if status == StatusCode :: CONFLICT // pypiserver (https://pypi.org/project/pypiserver) // PyPI / TestPyPI || ( status == StatusCode :: BAD_REQUEST && err_text . contains ( " " ) ) // Nexus Repository OSS (https://www.sonatype.com/nexus-repository-oss) || ( status == StatusCode :: BAD_REQUEST && err_text . contains ( " " ) ) // # Gitlab Enterprise Edition (https://about.gitlab.com) || ( status == StatusCode :: BAD_REQUEST && err_text . contains ( " " ) ) { Err ( UploadError :: FileExistsError ( err_text ) ) } else { Err ( UploadError :: StatusCodeError ( status_string , err_text ) ) } } }
Uploads a single wheel to the registry
Uploads a single wheel to the registry
registry
&Registry
wheel_path
&Path
metadata21
&[(String, String)]
supported_version
&str
registry
&Registry
wheel_path
&Path
metadata21
&[(String, String)]
supported_version
&str