@@ -7,6 +7,8 @@ use reqwest::header;
77use reqwest:: header:: { HeaderMap , HeaderValue } ;
88use secrecy:: { ExposeSecret , SecretString } ;
99use serde:: Serialize ;
10+ use serde:: de:: DeserializeOwned ;
11+ use std:: collections:: HashMap ;
1012use std:: fmt:: { Display , Formatter } ;
1113
1214// OpenAPI spec: https://crates.io/api/openapi.json
@@ -42,26 +44,50 @@ impl CratesIoApi {
4244 self . dry_run
4345 }
4446
45- /// List existing trusted publishing configurations for a given crate.
46- pub ( crate ) fn list_trusted_publishing_github_configs (
47- & self ,
48- krate : & str ,
49- ) -> anyhow:: Result < Vec < TrustedPublishingGitHubConfig > > {
47+ /// Return the user ID based on the username.
48+ pub ( crate ) fn get_user_id ( & self , username : & str ) -> anyhow:: Result < UserId > {
5049 #[ derive( serde:: Deserialize ) ]
51- struct GetTrustedPublishing {
52- github_configs : Vec < TrustedPublishingGitHubConfig > ,
50+ struct User {
51+ id : u32 ,
5352 }
5453
55- let response: GetTrustedPublishing = self
54+ #[ derive( serde:: Deserialize ) ]
55+ struct UserResponse {
56+ user : User ,
57+ }
58+
59+ let response: UserResponse = self
5660 . req :: < ( ) > (
5761 reqwest:: Method :: GET ,
58- & format ! ( "/trusted_publishing/github_configs?crate={krate}" ) ,
62+ & format ! ( "/users/{username}" ) ,
63+ HashMap :: new ( ) ,
5964 None ,
6065 ) ?
6166 . error_for_status ( ) ?
6267 . json_annotated ( ) ?;
6368
64- Ok ( response. github_configs )
69+ Ok ( UserId ( response. user . id ) )
70+ }
71+
72+ /// List existing trusted publishing configurations for a given crate.
73+ pub ( crate ) fn list_trusted_publishing_github_configs (
74+ & self ,
75+ user_id : UserId ,
76+ ) -> anyhow:: Result < Vec < TrustedPublishingGitHubConfig > > {
77+ #[ derive( serde:: Deserialize ) ]
78+ struct GetTrustedPublishing {
79+ github_configs : Vec < TrustedPublishingGitHubConfig > ,
80+ }
81+
82+ let mut configs = vec ! [ ] ;
83+ self . req_paged :: < ( ) , GetTrustedPublishing , _ > (
84+ "/trusted_publishing/github_configs" ,
85+ HashMap :: from ( [ ( "user_id" . to_string ( ) , user_id. 0 . to_string ( ) ) ] ) ,
86+ None ,
87+ |resp| configs. extend ( resp. github_configs ) ,
88+ ) ?;
89+
90+ Ok ( configs)
6591 }
6692
6793 /// Create a new trusted publishing configuration for a given crate.
@@ -110,6 +136,7 @@ impl CratesIoApi {
110136 self . req (
111137 reqwest:: Method :: POST ,
112138 "/trusted_publishing/github_configs" ,
139+ HashMap :: new ( ) ,
113140 Some ( & request) ,
114141 ) ?
115142 . error_for_status ( )
@@ -129,6 +156,7 @@ impl CratesIoApi {
129156 self . req :: < ( ) > (
130157 reqwest:: Method :: DELETE ,
131158 & format ! ( "/trusted_publishing/github_configs/{}" , id. 0 ) ,
159+ HashMap :: new ( ) ,
132160 None ,
133161 ) ?
134162 . error_for_status ( )
@@ -138,20 +166,24 @@ impl CratesIoApi {
138166 Ok ( ( ) )
139167 }
140168
141- /// Get information about a crate .
142- pub ( crate ) fn get_crate ( & self , krate : & str ) -> anyhow:: Result < CratesIoCrate > {
169+ /// Return all crates owned by the given user .
170+ pub ( crate ) fn get_crates_owned_by ( & self , user : UserId ) -> anyhow:: Result < Vec < CratesIoCrate > > {
143171 #[ derive( serde:: Deserialize ) ]
144- struct CrateResponse {
145- #[ serde( rename = "crate" ) ]
146- krate : CratesIoCrate ,
172+ struct CratesResponse {
173+ crates : Vec < CratesIoCrate > ,
147174 }
148175
149- let response: CrateResponse = self
150- . req :: < ( ) > ( reqwest:: Method :: GET , & format ! ( "/crates/{krate}" ) , None ) ?
151- . error_for_status ( ) ?
152- . json_annotated ( ) ?;
176+ let mut crates = vec ! [ ] ;
177+ self . req_paged :: < ( ) , CratesResponse , _ > (
178+ "/crates" ,
179+ HashMap :: from ( [ ( "user_id" . to_string ( ) , user. 0 . to_string ( ) ) ] ) ,
180+ None ,
181+ |res| {
182+ crates. extend ( res. crates ) ;
183+ } ,
184+ ) ?;
153185
154- Ok ( response . krate )
186+ Ok ( crates )
155187 }
156188
157189 /// Enable or disable the `trustpub_only` crate option, which specifies whether a crate
@@ -176,6 +208,7 @@ impl CratesIoApi {
176208 self . req (
177209 reqwest:: Method :: PATCH ,
178210 & format ! ( "/crates/{krate}" ) ,
211+ HashMap :: new ( ) ,
179212 Some ( & PatchCrateRequest {
180213 krate : Crate {
181214 trustpub_only : value,
@@ -194,20 +227,76 @@ impl CratesIoApi {
194227 & self ,
195228 method : reqwest:: Method ,
196229 path : & str ,
230+ query : HashMap < String , String > ,
197231 data : Option < & T > ,
198232 ) -> anyhow:: Result < reqwest:: blocking:: Response > {
199233 let mut req = self
200234 . client
201235 . request ( method, format ! ( "{CRATES_IO_BASE_URL}{path}" ) )
202- . bearer_auth ( self . token . expose_secret ( ) ) ;
236+ . bearer_auth ( self . token . expose_secret ( ) )
237+ . query ( & query) ;
203238 if let Some ( data) = data {
204239 req = req. json ( data) ;
205240 }
206241
207242 Ok ( req. send ( ) ?)
208243 }
244+
245+ /// Fetch a resource that is paged.
246+ fn req_paged < T : Serialize , R : DeserializeOwned , F > (
247+ & self ,
248+ path : & str ,
249+ mut query : HashMap < String , String > ,
250+ data : Option < & T > ,
251+ mut handle_response : F ,
252+ ) -> anyhow:: Result < ( ) >
253+ where
254+ F : FnMut ( R ) ,
255+ {
256+ #[ derive( serde:: Deserialize , Debug ) ]
257+ struct Response < R > {
258+ meta : MetaResponse ,
259+ #[ serde( flatten) ]
260+ data : R ,
261+ }
262+
263+ #[ derive( serde:: Deserialize , Debug ) ]
264+ struct MetaResponse {
265+ next_page : Option < String > ,
266+ }
267+
268+ if !query. contains_key ( "per_page" ) {
269+ query. insert ( "per_page" . to_string ( ) , "100" . to_string ( ) ) ;
270+ }
271+
272+ let mut query = query;
273+ let mut path_extra: Option < String > = None ;
274+ loop {
275+ let path = match path_extra {
276+ Some ( p) => format ! ( "{path}{p}" ) ,
277+ None => path. to_owned ( ) ,
278+ } ;
279+
280+ let response: Response < R > = self
281+ . req ( reqwest:: Method :: GET , & path, query, data) ?
282+ . error_for_status ( ) ?
283+ . json_annotated ( ) ?;
284+ handle_response ( response. data ) ;
285+ match response. meta . next_page {
286+ Some ( next) => {
287+ path_extra = Some ( next) ;
288+ query = HashMap :: new ( ) ;
289+ }
290+ None => break ,
291+ }
292+ }
293+ Ok ( ( ) )
294+ }
209295}
210296
297+ #[ derive( Copy , Clone , Debug ) ]
298+ pub struct UserId ( pub u32 ) ;
299+
211300#[ derive( serde:: Deserialize , Copy , Clone , Debug , PartialEq , Eq , PartialOrd , Ord ) ]
212301pub struct TrustedPublishingId ( u64 ) ;
213302
@@ -217,8 +306,10 @@ impl Display for TrustedPublishingId {
217306 }
218307}
219308
220- #[ derive( serde:: Deserialize , Debug ) ]
309+ #[ derive( serde:: Deserialize , Debug , Clone ) ]
221310pub ( crate ) struct TrustedPublishingGitHubConfig {
311+ #[ serde( rename = "crate" ) ]
312+ pub ( crate ) krate : String ,
222313 pub ( crate ) id : TrustedPublishingId ,
223314 pub ( crate ) repository_owner : String ,
224315 pub ( crate ) repository_name : String ,
@@ -228,6 +319,7 @@ pub(crate) struct TrustedPublishingGitHubConfig {
228319
229320#[ derive( serde:: Deserialize , Debug ) ]
230321pub ( crate ) struct CratesIoCrate {
322+ pub ( crate ) name : String ,
231323 #[ serde( rename = "trustpub_only" ) ]
232324 pub ( crate ) trusted_publishing_only : bool ,
233325}
0 commit comments