Skip to content

Commit

Permalink
Add APIs for changing package owners and rename revert_package to rem…
Browse files Browse the repository at this point in the history
…ove_owner

It turns out that revert_package_request was actually for removing owners.
I have tried to mitigate these mistakes in the future by making documentation over at gleam-lang#28 which has a somewhat reliable process for figuring out how the API works.

Part of gleam-lang/gleam#3155
  • Loading branch information
Pi-Cla committed May 22, 2024
1 parent 4ea7550 commit e8403b4
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 9 deletions.
117 changes: 111 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ use regex::Regex;
use ring::digest::{Context, SHA256};
use serde::Deserialize;
use serde_json::json;
use std::{collections::HashMap, convert::TryFrom, convert::TryInto, io::BufReader};
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
fmt::Display,
io::BufReader,
};
use thiserror::Error;
use version::{Range, Version};
use x509_parser::prelude::FromDer;
Expand Down Expand Up @@ -489,23 +494,123 @@ pub fn revert_release_response(response: http::Response<Vec<u8>>) -> Result<(),
}
}

pub fn revert_package_request(
/// See: https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L47
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum OwnerLevel {
/// Has every package permission EXCEPT the ability to change who owns the package
Maintainer,
/// Has every package permission including the ability to change who owns the package
Full,
}

impl Display for OwnerLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OwnerLevel::Maintainer => write!(f, "maintainer"),
OwnerLevel::Full => write!(f, "full"),
}
}
}

/// API Docs:
///
/// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L107
///
/// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L19
pub fn add_owner_request(
package_name: &str,
owner: &str,
level: OwnerLevel,
api_key: &str,
config: &Config,
) -> Result<http::Request<Vec<u8>>, ApiError> {
Ok(config
) -> http::Request<Vec<u8>> {
let body = json!({
"level": level.to_string(),
"transfer": false,
});

config
.api_request(
Method::PUT,
&format!("packages/{}/owners/{}", package_name, owner),
Some(api_key),
)
.body(body.to_string().into_bytes())
.expect("add_owner_request request")
}

pub fn add_owner_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
let (parts, body) = response.into_parts();
match parts.status {
StatusCode::NO_CONTENT => Ok(()),
StatusCode::NOT_FOUND => Err(ApiError::NotFound),
StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited),
StatusCode::UNAUTHORIZED => Err(ApiError::InvalidApiKey),
StatusCode::FORBIDDEN => Err(ApiError::Forbidden),
status => Err(ApiError::unexpected_response(status, body)),
}
}

/// API Docs:
///
/// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L125
///
/// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L19
pub fn transfer_owner_request(
package_name: &str,
owner: &str,
api_key: &str,
config: &Config,
) -> http::Request<Vec<u8>> {
let body = json!({
"level": OwnerLevel::Full.to_string(),
"transfer": true,
});

config
.api_request(
Method::PUT,
&format!("packages/{}/owners/{}", package_name, owner),
Some(api_key),
)
.body(body.to_string().into_bytes())
.expect("transfer_owner_request request")
}

pub fn transfer_owner_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
let (parts, body) = response.into_parts();
match parts.status {
StatusCode::NO_CONTENT => Ok(()),
StatusCode::NOT_FOUND => Err(ApiError::NotFound),
StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited),
StatusCode::UNAUTHORIZED => Err(ApiError::InvalidApiKey),
StatusCode::FORBIDDEN => Err(ApiError::Forbidden),
status => Err(ApiError::unexpected_response(status, body)),
}
}

/// API Docs:
///
/// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L139
///
/// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L28
pub fn remove_owner_request(
package_name: &str,
owner: &str,
api_key: &str,
config: &Config,
) -> http::Request<Vec<u8>> {
config
.api_request(
Method::DELETE,
&format!("packages/{}/owners/{}", package_name, owner),
Some(api_key),
)
.body(vec![])
.expect("revert_package_request request"))
.expect("remove_owner_request request")
}

pub fn revert_package_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
pub fn remove_owner_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
let (parts, body) = response.into_parts();
match parts.status {
StatusCode::NO_CONTENT => Ok(()),
Expand Down
83 changes: 80 additions & 3 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,84 @@ async fn revert_release_success() {
}

#[tokio::test]
async fn revert_package_success() {
async fn add_owner_success() {
let key = "my-api-key-here";
let package = "gleam_experimental_stdlib";
let owner = "lpil";
let level = OwnerLevel::Maintainer;

let mut server = mockito::Server::new_async().await;
let mock = server
.mock(
"PUT",
format!("/packages/{}/owners/{}", package, owner).as_str(),
)
.expect(1)
.match_header("authorization", key)
.match_header("accept", "application/json")
.match_body(Matcher::Json(json!({
"level": "maintainer",
"transfer": false,
})))
.with_status(204)
.create_async()
.await;

let mut config = Config::new();
config.api_base = http::Uri::try_from(server.url()).unwrap();

let result = crate::add_owner_response(
http_send(crate::add_owner_request(
package, owner, level, key, &config,
))
.await
.unwrap(),
)
.unwrap();

assert_eq!(result, ());
mock.assert();
}

#[tokio::test]
async fn transfer_owner_success() {
let key = "my-api-key-here";
let package = "gleam_experimental_stdlib";
let owner = "lpil";

let mut server = mockito::Server::new_async().await;
let mock = server
.mock(
"PUT",
format!("/packages/{}/owners/{}", package, owner).as_str(),
)
.expect(1)
.match_header("authorization", key)
.match_header("accept", "application/json")
.match_body(Matcher::Json(json!({
"level": "full",
"transfer": true,
})))
.with_status(204)
.create_async()
.await;

let mut config = Config::new();
config.api_base = http::Uri::try_from(server.url()).unwrap();

let result = crate::transfer_owner_response(
http_send(crate::transfer_owner_request(package, owner, key, &config))
.await
.unwrap(),
)
.unwrap();

assert_eq!(result, ());
mock.assert();
}

#[tokio::test]
async fn remove_owner_success() {
let key = "my-api-key-here";
let package = "gleam_experimental_stdlib";
let owner = "lpil";
Expand All @@ -250,8 +327,8 @@ async fn revert_package_success() {
let mut config = Config::new();
config.api_base = http::Uri::try_from(server.url()).unwrap();

let result = crate::revert_package_response(
http_send(crate::revert_package_request(package, owner, key, &config).unwrap())
let result = crate::remove_owner_response(
http_send(crate::remove_owner_request(package, owner, key, &config))
.await
.unwrap(),
)
Expand Down

0 comments on commit e8403b4

Please sign in to comment.