@@ -98,8 +98,11 @@ impl RepositoryForge for GitHub {
98
98
"repo" : name. repo,
99
99
} ) ,
100
100
) ?;
101
- if let Some ( repo) = response. data . repository {
102
- Ok ( Some ( Repository {
101
+
102
+ Ok ( response
103
+ . data
104
+ . and_then ( |data| data. repository )
105
+ . map ( |repo| Repository {
103
106
id : repo. id ,
104
107
name_with_owner : repo. name_with_owner ,
105
108
description : repo. description ,
@@ -108,9 +111,6 @@ impl RepositoryForge for GitHub {
108
111
forks : repo. fork_count ,
109
112
issues : repo. issues . total_count ,
110
113
} ) )
111
- } else {
112
- Ok ( None )
113
- }
114
114
}
115
115
116
116
fn fetch_repositories ( & self , node_ids : & [ String ] ) -> Result < FetchRepositoriesResult > {
@@ -123,12 +123,14 @@ impl RepositoryForge for GitHub {
123
123
124
124
// The error is returned *before* we reach the rate limit, to ensure we always have an
125
125
// amount of API calls we can make at any time.
126
- trace ! (
127
- "GitHub GraphQL rate limit remaining: {}" ,
128
- response. data. rate_limit. remaining
129
- ) ;
130
- if response. data . rate_limit . remaining < self . github_updater_min_rate_limit {
131
- return Err ( RateLimitReached . into ( ) ) ;
126
+ if let Some ( ref data) = response. data {
127
+ trace ! (
128
+ "GitHub GraphQL rate limit remaining: {}" ,
129
+ data. rate_limit. remaining
130
+ ) ;
131
+ if data. rate_limit . remaining < self . github_updater_min_rate_limit {
132
+ return Err ( RateLimitReached . into ( ) ) ;
133
+ }
132
134
}
133
135
134
136
let mut ret = FetchRepositoriesResult :: default ( ) ;
@@ -139,23 +141,29 @@ impl RepositoryForge for GitHub {
139
141
( "NOT_FOUND" , [ Segment ( nodes) , Index ( idx) ] ) if nodes == "nodes" => {
140
142
ret. missing . push ( node_ids[ * idx as usize ] . clone ( ) ) ;
141
143
}
144
+ ( "RATE_LIMITED" , [ ] ) => {
145
+ return Err ( RateLimitReached . into ( ) ) ;
146
+ }
142
147
_ => failure:: bail!( "error updating repositories: {}" , error. message) ,
143
148
}
144
149
}
145
- // When a node is missing (for example if the repository was deleted or made private) the
146
- // GraphQL API will return *both* a `null` instead of the data in the nodes list and a
147
- // `NOT_FOUND` error in the errors list.
148
- for node in response. data . nodes . into_iter ( ) . flatten ( ) {
149
- let repo = Repository {
150
- id : node. id ,
151
- name_with_owner : node. name_with_owner ,
152
- description : node. description ,
153
- last_activity_at : node. pushed_at ,
154
- stars : node. stargazer_count ,
155
- forks : node. fork_count ,
156
- issues : node. issues . total_count ,
157
- } ;
158
- ret. present . insert ( repo. id . clone ( ) , repo) ;
150
+
151
+ if let Some ( data) = response. data {
152
+ // When a node is missing (for example if the repository was deleted or made private) the
153
+ // GraphQL API will return *both* a `null` instead of the data in the nodes list and a
154
+ // `NOT_FOUND` error in the errors list.
155
+ for node in data. nodes . into_iter ( ) . flatten ( ) {
156
+ let repo = Repository {
157
+ id : node. id ,
158
+ name_with_owner : node. name_with_owner ,
159
+ description : node. description ,
160
+ last_activity_at : node. pushed_at ,
161
+ stars : node. stargazer_count ,
162
+ forks : node. fork_count ,
163
+ issues : node. issues . total_count ,
164
+ } ;
165
+ ret. present . insert ( repo. id . clone ( ) , repo) ;
166
+ }
159
167
}
160
168
161
169
Ok ( ret)
@@ -190,7 +198,7 @@ impl GitHub {
190
198
191
199
#[ derive( Debug , Deserialize ) ]
192
200
struct GraphResponse < T > {
193
- data : T ,
201
+ data : Option < T > ,
194
202
#[ serde( default ) ]
195
203
errors : Vec < GraphError > ,
196
204
}
@@ -199,6 +207,7 @@ struct GraphResponse<T> {
199
207
struct GraphError {
200
208
#[ serde( rename = "type" ) ]
201
209
error_type : String ,
210
+ #[ serde( default ) ]
202
211
path : Vec < GraphErrorPath > ,
203
212
message : String ,
204
213
}
@@ -252,7 +261,29 @@ mod tests {
252
261
use mockito:: mock;
253
262
254
263
#[ test]
255
- fn test_rate_limit ( ) {
264
+ fn test_rate_limit_fail ( ) {
265
+ crate :: test:: wrapper ( |env| {
266
+ let mut config = env. base_config ( ) ;
267
+ config. github_accesstoken = Some ( "qsjdnfqdq" . to_owned ( ) ) ;
268
+ let updater = GitHub :: new ( & config) . expect ( "GitHub::new failed" ) . unwrap ( ) ;
269
+
270
+ let _m1 = mock ( "POST" , "/graphql" )
271
+ . with_header ( "content-type" , "application/json" )
272
+ . with_body (
273
+ r#"{"errors":[{"type":"RATE_LIMITED","message":"API rate limit exceeded"}]}"# ,
274
+ )
275
+ . create ( ) ;
276
+
277
+ match updater. fetch_repositories ( & [ String :: new ( ) ] ) {
278
+ Err ( e) if format ! ( "{:?}" , e) . contains ( "RateLimitReached" ) => { }
279
+ x => panic ! ( "Expected Err(RateLimitReached), found: {:?}" , x) ,
280
+ }
281
+ Ok ( ( ) )
282
+ } ) ;
283
+ }
284
+
285
+ #[ test]
286
+ fn test_rate_limit_manual ( ) {
256
287
crate :: test:: wrapper ( |env| {
257
288
let mut config = env. base_config ( ) ;
258
289
config. github_accesstoken = Some ( "qsjdnfqdq" . to_owned ( ) ) ;
0 commit comments