Skip to content

Commit 78d6981

Browse files
authored
Merge pull request #82 from chondent/fix-invalid-github-url
Parse and fix broken github api urls
2 parents dbabfc3 + b1b4cf2 commit 78d6981

File tree

6 files changed

+148
-1
lines changed

6 files changed

+148
-1
lines changed

src/main/java/com/spotify/github/v3/repos/Branch.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
@Value.Immutable
3535
@GithubStyle
3636
@JsonSerialize(as = ImmutableBranch.class)
37-
@JsonDeserialize(as = ImmutableBranch.class)
37+
@JsonDeserialize(as = ImmutableBranch.class, using = BranchDeserializer.class)
3838
public interface Branch {
3939

4040
/** Branch name */
@@ -52,3 +52,4 @@ public interface Branch {
5252
/** Branch protection API URL */
5353
Optional<URI> protectionUrl();
5454
}
55+
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*-
2+
* -\-\-
3+
* github-api
4+
* --
5+
* Copyright (C) 2016 - 2020 Spotify AB
6+
* --
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* -/-/-
19+
*/
20+
21+
package com.spotify.github.v3.repos;
22+
23+
import com.fasterxml.jackson.core.JsonParser;
24+
import com.fasterxml.jackson.databind.DeserializationContext;
25+
import com.fasterxml.jackson.databind.JsonDeserializer;
26+
import com.fasterxml.jackson.databind.JsonNode;
27+
import com.spotify.github.v3.git.ImmutableShaLink;
28+
import java.io.IOException;
29+
import java.net.URI;
30+
import java.net.URISyntaxException;
31+
import java.net.URLEncoder;
32+
import java.nio.charset.StandardCharsets;
33+
34+
public class BranchDeserializer extends JsonDeserializer<ImmutableBranch> {
35+
36+
private URI fixInvalidGithubUrl(final String invalidUrl) throws IOException {
37+
// There's a bug in github where it gives you back non-url-encoded characters
38+
// in the protection_url field. For example if your branch has a single "%" in its name.
39+
// As of this writing, the protection URL looks something like this
40+
// https://github-server.tld/api/v3/repos/owner/repo-name/branches/branch-name/protection
41+
final String[] schemaAndPath = invalidUrl.split("//");
42+
String[] pathParts = schemaAndPath[1].split("/");
43+
for (int i = 0; i < pathParts.length; i++) {
44+
pathParts[i] = URLEncoder.encode(pathParts[i], StandardCharsets.UTF_8);
45+
}
46+
String fixedUrlString = schemaAndPath[0] + "//" + String.join("/", pathParts);
47+
return URI.create(fixedUrlString);
48+
}
49+
50+
@Override
51+
public ImmutableBranch deserialize(final JsonParser jsonParser,
52+
final DeserializationContext deserializationContext)
53+
throws IOException {
54+
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
55+
URI protectionUrl;
56+
var immutableBranchBuilder = ImmutableBranch.builder();
57+
if (node.has("protected")) {
58+
immutableBranchBuilder.isProtected(node.get("protected").asBoolean());
59+
String protectionUrlString = node.get("protection_url").asText();
60+
try {
61+
protectionUrl = new URI(protectionUrlString);
62+
} catch (URISyntaxException e) {
63+
protectionUrl = fixInvalidGithubUrl(protectionUrlString);
64+
}
65+
immutableBranchBuilder.protectionUrl(protectionUrl);
66+
}
67+
ImmutableShaLink shaLink = ImmutableShaLink.builder()
68+
.sha(node.get("commit").get("sha").asText())
69+
.url(URI.create(node.at("/commit/url").asText()))
70+
.build();
71+
return immutableBranchBuilder
72+
.name(node.get("name").textValue())
73+
.commit(shaLink)
74+
.build();
75+
}
76+
}

src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,52 @@ public void getBranch() throws Exception {
249249
when(github.request("/repos/someowner/somerepo/branches/somebranch", Branch.class))
250250
.thenReturn(fixture);
251251
final Branch branch = repoClient.getBranch("somebranch").get();
252+
assertThat(branch.isProtected().orElse(false), is(true));
252253
assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e"));
254+
assertThat(
255+
branch.commit().url().toString(),
256+
is("https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"));
257+
}
258+
259+
@Test
260+
public void getBranchWithoutProtection() throws Exception {
261+
// Make sure the custom deserialiser correctly handles the optional protected fields
262+
final CompletableFuture<Branch> fixture =
263+
completedFuture(json.fromJson(getFixture("branch-not-protected.json"), Branch.class));
264+
when(github.request("/repos/someowner/somerepo/branches/somebranch", Branch.class))
265+
.thenReturn(fixture);
266+
final Branch branch = repoClient.getBranch("somebranch").get();
267+
assertThat(branch.isProtected().orElse(false), is(false));
268+
assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e"));
269+
assertThat(
270+
branch.commit().url().toString(),
271+
is("https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"));
272+
}
273+
274+
@Test
275+
public void getBranchWithCharactersIncorrectlyUnescapedByTheGithubApi() throws Exception {
276+
final CompletableFuture<Branch> fixture =
277+
completedFuture(json.fromJson(getFixture("branch-escape-chars.json"), Branch.class));
278+
when(github.request("/repos/someowner/somerepo/branches/unescaped-percent-sign-%", Branch.class))
279+
.thenReturn(fixture);
280+
final Branch branch = repoClient.getBranch("unescaped-percent-sign-%").get();
281+
assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e"));
282+
assertThat(
283+
branch.protectionUrl().get().toString(),
284+
is("https://api.github.com/repos/octocat/Hello-World/branches/unescaped-percent-sign-%25/protection"));
285+
}
286+
287+
@Test
288+
public void getBranchWithCharactersIncorrectlyUnescapedByTheGithubApi_uriVariationTwo() throws Exception {
289+
final CompletableFuture<Branch> fixture =
290+
completedFuture(json.fromJson(getFixture("branch-escape-chars-url-variation-two.json"), Branch.class));
291+
when(github.request("/repos/someowner/somerepo/branches/unescaped-percent-sign-%", Branch.class))
292+
.thenReturn(fixture);
293+
final Branch branch = repoClient.getBranch("unescaped-percent-sign-%").get();
294+
assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e"));
295+
assertThat(
296+
branch.protectionUrl().get().toString(),
297+
is("https://api.github.com/api/v3/repos/octocat/Hello-World/branches/branch-name-with-slashes/unescaped-percent-sign-%25/protection"));
253298
}
254299

255300
@Test
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "unescaped-percent-sign-%",
3+
"commit": {
4+
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
5+
"url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"
6+
},
7+
"protected": true,
8+
"protection_url": "https://api.github.com/api/v3/repos/octocat/Hello-World/branches/branch-name-with-slashes/unescaped-percent-sign-%/protection"
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "unescaped-percent-sign-%",
3+
"commit": {
4+
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
5+
"url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"
6+
},
7+
"protected": true,
8+
"protection_url": "https://api.github.com/repos/octocat/Hello-World/branches/unescaped-percent-sign-%/protection"
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "master",
3+
"commit": {
4+
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
5+
"url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"
6+
}
7+
}

0 commit comments

Comments
 (0)