Skip to content

Commit ee32cb3

Browse files
authored
Merge pull request #1722 from Kobzol/backport-sync-team
Backport sync team changes
2 parents d45a410 + 2f91501 commit ee32cb3

File tree

5 files changed

+173
-96
lines changed

5 files changed

+173
-96
lines changed

sync-team/src/github/api/mod.rs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,46 @@ impl HttpClient {
9292
}
9393
}
9494

95+
/// Send a request to the GitHub API and return the response.
9596
fn graphql<R, V>(&self, query: &str, variables: V, org: &str) -> anyhow::Result<R>
97+
where
98+
R: serde::de::DeserializeOwned,
99+
V: serde::Serialize,
100+
{
101+
let res = self.send_graphql_req(query, variables, org)?;
102+
103+
if let Some(error) = res.errors.first() {
104+
bail!("graphql error: {}", error.message);
105+
}
106+
107+
read_graphql_data(res)
108+
}
109+
110+
/// Send a request to the GitHub API and return the response.
111+
/// If the request contains the error type `NOT_FOUND`, this method returns `Ok(None)`.
112+
fn graphql_opt<R, V>(&self, query: &str, variables: V, org: &str) -> anyhow::Result<Option<R>>
113+
where
114+
R: serde::de::DeserializeOwned,
115+
V: serde::Serialize,
116+
{
117+
let res = self.send_graphql_req(query, variables, org)?;
118+
119+
if let Some(error) = res.errors.first() {
120+
if error.type_ == Some(GraphErrorType::NotFound) {
121+
return Ok(None);
122+
}
123+
bail!("graphql error: {}", error.message);
124+
}
125+
126+
read_graphql_data(res)
127+
}
128+
129+
fn send_graphql_req<R, V>(
130+
&self,
131+
query: &str,
132+
variables: V,
133+
org: &str,
134+
) -> anyhow::Result<GraphResult<R>>
96135
where
97136
R: serde::de::DeserializeOwned,
98137
V: serde::Serialize,
@@ -105,19 +144,13 @@ impl HttpClient {
105144
let resp = self
106145
.req(Method::POST, &GitHubUrl::new("graphql", org))?
107146
.json(&Request { query, variables })
108-
.send()?
147+
.send()
148+
.context("failed to send graphql request")?
109149
.custom_error_for_status()?;
110150

111-
let res: GraphResult<R> = resp.json_annotated().with_context(|| {
151+
resp.json_annotated().with_context(|| {
112152
format!("Failed to decode response body on graphql request with query '{query}'")
113-
})?;
114-
if let Some(error) = res.errors.first() {
115-
bail!("graphql error: {}", error.message);
116-
} else if let Some(data) = res.data {
117-
Ok(data)
118-
} else {
119-
bail!("missing graphql data");
120-
}
153+
})
121154
}
122155

123156
fn rest_paginated<F, T>(&self, method: &Method, url: &GitHubUrl, mut f: F) -> anyhow::Result<()>
@@ -159,6 +192,17 @@ impl HttpClient {
159192
}
160193
}
161194

195+
fn read_graphql_data<R>(res: GraphResult<R>) -> anyhow::Result<R>
196+
where
197+
R: serde::de::DeserializeOwned,
198+
{
199+
if let Some(data) = res.data {
200+
Ok(data)
201+
} else {
202+
bail!("missing graphql data");
203+
}
204+
}
205+
162206
fn allow_not_found(resp: Response, method: Method, url: &str) -> Result<(), anyhow::Error> {
163207
match resp.status() {
164208
StatusCode::NOT_FOUND => {
@@ -180,9 +224,19 @@ struct GraphResult<T> {
180224

181225
#[derive(Debug, serde::Deserialize)]
182226
struct GraphError {
227+
#[serde(rename = "type")]
228+
type_: Option<GraphErrorType>,
183229
message: String,
184230
}
185231

232+
#[derive(Debug, serde::Deserialize, PartialEq, Eq)]
233+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
234+
enum GraphErrorType {
235+
NotFound,
236+
#[serde(other)]
237+
Other,
238+
}
239+
186240
#[derive(serde::Deserialize)]
187241
struct GraphNodes<T> {
188242
nodes: Vec<Option<T>>,

sync-team/src/github/api/read.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::github::api::{
22
BranchProtection, GraphNode, GraphNodes, GraphPageInfo, HttpClient, Login, Repo, RepoTeam,
33
RepoUser, Team, TeamMember, TeamRole, team_node_id, url::GitHubUrl, user_node_id,
44
};
5+
use anyhow::Context as _;
56
use reqwest::Method;
67
use std::collections::{HashMap, HashSet};
78

@@ -271,16 +272,19 @@ impl GithubRead for GitHubApiRead {
271272
is_archived: bool,
272273
}
273274

274-
let result: Wrapper = self.client.graphql(
275-
QUERY,
276-
Params {
277-
owner: org,
278-
name: repo,
279-
},
280-
org,
281-
)?;
275+
let result: Option<Wrapper> = self
276+
.client
277+
.graphql_opt(
278+
QUERY,
279+
Params {
280+
owner: org,
281+
name: repo,
282+
},
283+
org,
284+
)
285+
.with_context(|| format!("failed to retrieve repo `{org}/{repo}`"))?;
282286

283-
let repo = result.repository.map(|repo_response| Repo {
287+
let repo = result.and_then(|r| r.repository).map(|repo_response| Repo {
284288
node_id: repo_response.id,
285289
name: repo.to_string(),
286290
description: repo_response.description.unwrap_or_default(),

sync-team/src/github/tests/mod.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::github::tests::test_utils::{BranchProtectionBuilder, DataModel, RepoData, TeamData};
1+
use crate::github::tests::test_utils::{
2+
BranchProtectionBuilder, DEFAULT_ORG, DataModel, RepoData, TeamData,
3+
};
24
use rust_team_data::v1::{BranchProtectionMode, RepoPermission};
35

46
mod test_utils;
@@ -17,7 +19,7 @@ fn team_create() {
1719
let user = model.create_user("mark");
1820
let user2 = model.create_user("jan");
1921
let gh = model.gh_model();
20-
model.create_team(TeamData::new("admins").gh_team("admins-gh", &[user, user2]));
22+
model.create_team(TeamData::new("admins").gh_team(DEFAULT_ORG, "admins-gh", &[user, user2]));
2123
let team_diff = model.diff_teams(gh);
2224
insta::assert_debug_snapshot!(team_diff, @r###"
2325
[
@@ -48,7 +50,7 @@ fn team_add_member() {
4850
let mut model = DataModel::default();
4951
let user = model.create_user("mark");
5052
let user2 = model.create_user("jan");
51-
model.create_team(TeamData::new("admins").gh_team("admins-gh", &[user]));
53+
model.create_team(TeamData::new("admins").gh_team(DEFAULT_ORG, "admins-gh", &[user]));
5254
let gh = model.gh_model();
5355

5456
model.get_team("admins").add_gh_member("admins-gh", user2);
@@ -85,11 +87,11 @@ fn team_dont_add_member_if_invitation_is_pending() {
8587
let mut model = DataModel::default();
8688
let user = model.create_user("mark");
8789
let user2 = model.create_user("jan");
88-
model.create_team(TeamData::new("admins").gh_team("admins-gh", &[user]));
90+
model.create_team(TeamData::new("admins").gh_team(DEFAULT_ORG, "admins-gh", &[user]));
8991
let mut gh = model.gh_model();
9092

9193
model.get_team("admins").add_gh_member("admins-gh", user2);
92-
gh.add_invitation("admins-gh", "jan");
94+
gh.add_invitation(DEFAULT_ORG, "admins-gh", "jan");
9395

9496
let team_diff = model.diff_teams(gh);
9597
insta::assert_debug_snapshot!(team_diff, @"[]");
@@ -100,7 +102,7 @@ fn team_remove_member() {
100102
let mut model = DataModel::default();
101103
let user = model.create_user("mark");
102104
let user2 = model.create_user("jan");
103-
model.create_team(TeamData::new("admins").gh_team("admins-gh", &[user, user2]));
105+
model.create_team(TeamData::new("admins").gh_team(DEFAULT_ORG, "admins-gh", &[user, user2]));
104106
let gh = model.gh_model();
105107

106108
model
@@ -142,8 +144,8 @@ fn team_delete() {
142144
// won't be generated, because no organization is known to scan for existing unmanaged teams.
143145
model.create_team(
144146
TeamData::new("admins")
145-
.gh_team("admins-gh", &[user])
146-
.gh_team("users-gh", &[user]),
147+
.gh_team(DEFAULT_ORG, "admins-gh", &[user])
148+
.gh_team(DEFAULT_ORG, "users-gh", &[user]),
147149
);
148150
let gh = model.gh_model();
149151

0 commit comments

Comments
 (0)