Skip to content

Commit 112f4a2

Browse files
Pi-Clalpil
authored andcommitted
Add APIs for changing package owners and rename revert_package to remove_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 #28 which has a somewhat reliable process for figuring out how the API works. Part of gleam-lang/gleam#3155
1 parent 745f81a commit 112f4a2

File tree

2 files changed

+191
-9
lines changed

2 files changed

+191
-9
lines changed

src/lib.rs

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ use regex::Regex;
1515
use ring::digest::{Context, SHA256};
1616
use serde::Deserialize;
1717
use serde_json::json;
18-
use std::{collections::HashMap, convert::TryFrom, convert::TryInto, io::BufReader};
18+
use std::{
19+
collections::HashMap,
20+
convert::{TryFrom, TryInto},
21+
fmt::Display,
22+
io::BufReader,
23+
};
1924
use thiserror::Error;
2025
use version::{Range, Version};
2126
use x509_parser::prelude::FromDer;
@@ -538,23 +543,123 @@ pub fn revert_release_response(response: http::Response<Vec<u8>>) -> Result<(),
538543
}
539544
}
540545

541-
pub fn revert_package_request(
546+
/// See: https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L47
547+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
548+
pub enum OwnerLevel {
549+
/// Has every package permission EXCEPT the ability to change who owns the package
550+
Maintainer,
551+
/// Has every package permission including the ability to change who owns the package
552+
Full,
553+
}
554+
555+
impl Display for OwnerLevel {
556+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557+
match self {
558+
OwnerLevel::Maintainer => write!(f, "maintainer"),
559+
OwnerLevel::Full => write!(f, "full"),
560+
}
561+
}
562+
}
563+
564+
/// API Docs:
565+
///
566+
/// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L107
567+
///
568+
/// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L19
569+
pub fn add_owner_request(
542570
package_name: &str,
543571
owner: &str,
572+
level: OwnerLevel,
544573
api_key: &str,
545574
config: &Config,
546-
) -> Result<http::Request<Vec<u8>>, ApiError> {
547-
Ok(config
575+
) -> http::Request<Vec<u8>> {
576+
let body = json!({
577+
"level": level.to_string(),
578+
"transfer": false,
579+
});
580+
581+
config
582+
.api_request(
583+
Method::PUT,
584+
&format!("packages/{}/owners/{}", package_name, owner),
585+
Some(api_key),
586+
)
587+
.body(body.to_string().into_bytes())
588+
.expect("add_owner_request request")
589+
}
590+
591+
pub fn add_owner_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
592+
let (parts, body) = response.into_parts();
593+
match parts.status {
594+
StatusCode::NO_CONTENT => Ok(()),
595+
StatusCode::NOT_FOUND => Err(ApiError::NotFound),
596+
StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited),
597+
StatusCode::UNAUTHORIZED => Err(ApiError::InvalidApiKey),
598+
StatusCode::FORBIDDEN => Err(ApiError::Forbidden),
599+
status => Err(ApiError::unexpected_response(status, body)),
600+
}
601+
}
602+
603+
/// API Docs:
604+
///
605+
/// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L125
606+
///
607+
/// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L19
608+
pub fn transfer_owner_request(
609+
package_name: &str,
610+
owner: &str,
611+
api_key: &str,
612+
config: &Config,
613+
) -> http::Request<Vec<u8>> {
614+
let body = json!({
615+
"level": OwnerLevel::Full.to_string(),
616+
"transfer": true,
617+
});
618+
619+
config
620+
.api_request(
621+
Method::PUT,
622+
&format!("packages/{}/owners/{}", package_name, owner),
623+
Some(api_key),
624+
)
625+
.body(body.to_string().into_bytes())
626+
.expect("transfer_owner_request request")
627+
}
628+
629+
pub fn transfer_owner_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
630+
let (parts, body) = response.into_parts();
631+
match parts.status {
632+
StatusCode::NO_CONTENT => Ok(()),
633+
StatusCode::NOT_FOUND => Err(ApiError::NotFound),
634+
StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited),
635+
StatusCode::UNAUTHORIZED => Err(ApiError::InvalidApiKey),
636+
StatusCode::FORBIDDEN => Err(ApiError::Forbidden),
637+
status => Err(ApiError::unexpected_response(status, body)),
638+
}
639+
}
640+
641+
/// API Docs:
642+
///
643+
/// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L139
644+
///
645+
/// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L28
646+
pub fn remove_owner_request(
647+
package_name: &str,
648+
owner: &str,
649+
api_key: &str,
650+
config: &Config,
651+
) -> http::Request<Vec<u8>> {
652+
config
548653
.api_request(
549654
Method::DELETE,
550655
&format!("packages/{}/owners/{}", package_name, owner),
551656
Some(api_key),
552657
)
553658
.body(vec![])
554-
.expect("publish_package_request request"))
659+
.expect("remove_owner_request request")
555660
}
556661

557-
pub fn revert_package_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
662+
pub fn remove_owner_response(response: http::Response<Vec<u8>>) -> Result<(), ApiError> {
558663
let (parts, body) = response.into_parts();
559664
match parts.status {
560665
StatusCode::NO_CONTENT => Ok(()),

src/tests.rs

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,84 @@ async fn revert_release_success() {
229229
}
230230

231231
#[tokio::test]
232-
async fn revert_package_success() {
232+
async fn add_owner_success() {
233+
let key = "my-api-key-here";
234+
let package = "gleam_experimental_stdlib";
235+
let owner = "lpil";
236+
let level = OwnerLevel::Maintainer;
237+
238+
let mut server = mockito::Server::new_async().await;
239+
let mock = server
240+
.mock(
241+
"PUT",
242+
format!("/packages/{}/owners/{}", package, owner).as_str(),
243+
)
244+
.expect(1)
245+
.match_header("authorization", key)
246+
.match_header("accept", "application/json")
247+
.match_body(Matcher::Json(json!({
248+
"level": "maintainer",
249+
"transfer": false,
250+
})))
251+
.with_status(204)
252+
.create_async()
253+
.await;
254+
255+
let mut config = Config::new();
256+
config.api_base = http::Uri::try_from(server.url()).unwrap();
257+
258+
let result = crate::add_owner_response(
259+
http_send(crate::add_owner_request(
260+
package, owner, level, key, &config,
261+
))
262+
.await
263+
.unwrap(),
264+
)
265+
.unwrap();
266+
267+
assert_eq!(result, ());
268+
mock.assert();
269+
}
270+
271+
#[tokio::test]
272+
async fn transfer_owner_success() {
273+
let key = "my-api-key-here";
274+
let package = "gleam_experimental_stdlib";
275+
let owner = "lpil";
276+
277+
let mut server = mockito::Server::new_async().await;
278+
let mock = server
279+
.mock(
280+
"PUT",
281+
format!("/packages/{}/owners/{}", package, owner).as_str(),
282+
)
283+
.expect(1)
284+
.match_header("authorization", key)
285+
.match_header("accept", "application/json")
286+
.match_body(Matcher::Json(json!({
287+
"level": "full",
288+
"transfer": true,
289+
})))
290+
.with_status(204)
291+
.create_async()
292+
.await;
293+
294+
let mut config = Config::new();
295+
config.api_base = http::Uri::try_from(server.url()).unwrap();
296+
297+
let result = crate::transfer_owner_response(
298+
http_send(crate::transfer_owner_request(package, owner, key, &config))
299+
.await
300+
.unwrap(),
301+
)
302+
.unwrap();
303+
304+
assert_eq!(result, ());
305+
mock.assert();
306+
}
307+
308+
#[tokio::test]
309+
async fn remove_owner_success() {
233310
let key = "my-api-key-here";
234311
let package = "gleam_experimental_stdlib";
235312
let owner = "lpil";
@@ -250,8 +327,8 @@ async fn revert_package_success() {
250327
let mut config = Config::new();
251328
config.api_base = http::Uri::try_from(server.url()).unwrap();
252329

253-
let result = crate::revert_package_response(
254-
http_send(crate::revert_package_request(package, owner, key, &config).unwrap())
330+
let result = crate::remove_owner_response(
331+
http_send(crate::remove_owner_request(package, owner, key, &config))
255332
.await
256333
.unwrap(),
257334
)

0 commit comments

Comments
 (0)