Skip to content

Commit 547597f

Browse files
committed
Fix parsing non-protected branches + simplify deserialization
In #82 we added a custom deserializer for the `Branch` class. This deserializer didn't handle certain cases properly that the GH API would return. In particular the case where the `protected` field was set and no `protection_url` is present would lead to a NPE in the deserializer. To avoid this error and make the whole parser more resilient to different API results, let's reduce the scope of the custom deserializer to only parse the `protection_url` field instead of the whole `Branch` class. There should be no change in runtime behavior apart from fixing the error cases.
1 parent 1155a5b commit 547597f

File tree

5 files changed

+45
-32
lines changed

5 files changed

+45
-32
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, using = BranchDeserializer.class)
37+
@JsonDeserialize(as = ImmutableBranch.class)
3838
public interface Branch {
3939

4040
/** Branch name */
@@ -50,6 +50,7 @@ public interface Branch {
5050
Optional<Boolean> isProtected();
5151

5252
/** Branch protection API URL */
53+
@JsonDeserialize(using = BranchProtectionUrlDeserializer.class)
5354
Optional<URI> protectionUrl();
5455
}
5556

src/main/java/com/spotify/github/v3/repos/BranchDeserializer.java renamed to src/main/java/com/spotify/github/v3/repos/BranchProtectionUrlDeserializer.java

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* -\-\-
33
* github-api
44
* --
5-
* Copyright (C) 2016 - 2020 Spotify AB
5+
* Copyright (C) 2016 - 2021 Spotify AB
66
* --
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
@@ -21,19 +21,19 @@
2121
package com.spotify.github.v3.repos;
2222

2323
import com.fasterxml.jackson.core.JsonParser;
24+
import com.fasterxml.jackson.core.type.TypeReference;
2425
import com.fasterxml.jackson.databind.DeserializationContext;
2526
import com.fasterxml.jackson.databind.JsonDeserializer;
26-
import com.fasterxml.jackson.databind.JsonNode;
27-
import com.spotify.github.v3.git.ImmutableShaLink;
2827
import java.io.IOException;
2928
import java.net.URI;
3029
import java.net.URISyntaxException;
3130
import java.net.URLEncoder;
3231
import java.nio.charset.StandardCharsets;
32+
import java.util.Optional;
3333

34-
public class BranchDeserializer extends JsonDeserializer<ImmutableBranch> {
34+
public class BranchProtectionUrlDeserializer extends JsonDeserializer<Optional<URI>> {
3535

36-
private URI fixInvalidGithubUrl(final String invalidUrl) throws IOException {
36+
private URI fixInvalidGithubUrl(final String invalidUrl) {
3737
// There's a bug in github where it gives you back non-url-encoded characters
3838
// in the protection_url field. For example if your branch has a single "%" in its name.
3939
// As of this writing, the protection URL looks something like this
@@ -48,29 +48,20 @@ private URI fixInvalidGithubUrl(final String invalidUrl) throws IOException {
4848
}
4949

5050
@Override
51-
public ImmutableBranch deserialize(final JsonParser jsonParser,
52-
final DeserializationContext deserializationContext)
51+
public Optional<URI> deserialize(
52+
final JsonParser jsonParser, final DeserializationContext deserializationContext)
5353
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();
54+
55+
TypeReference<Optional<String>> ref = new TypeReference<>() {};
56+
Optional<String> protectionUrlStringOpt = jsonParser.readValueAs(ref);
57+
58+
return protectionUrlStringOpt.map(
59+
protectionUrlString -> {
60+
try {
61+
return new URI(protectionUrlString);
62+
} catch (URISyntaxException e) {
63+
return fixInvalidGithubUrl(protectionUrlString);
64+
}
65+
});
7566
}
7667
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,21 +251,34 @@ public void getBranch() throws Exception {
251251
.thenReturn(fixture);
252252
final Branch branch = repoClient.getBranch("somebranch").get();
253253
assertThat(branch.isProtected().orElse(false), is(true));
254+
assertThat(branch.protectionUrl().get().toString(), is("https://api.github.com/repos/octocat/Hello-World/branches/master/protection"));
254255
assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e"));
255256
assertThat(
256257
branch.commit().url().toString(),
257258
is("https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"));
258259
}
259260

260261
@Test
261-
public void getBranchWithoutProtection() throws Exception {
262-
// Make sure the custom deserialiser correctly handles the optional protected fields
262+
public void getBranchWithNoProtection() throws Exception {
263263
final CompletableFuture<Branch> fixture =
264264
completedFuture(json.fromJson(getFixture("branch-not-protected.json"), Branch.class));
265265
when(github.request("/repos/someowner/somerepo/branches/somebranch", Branch.class))
266266
.thenReturn(fixture);
267267
final Branch branch = repoClient.getBranch("somebranch").get();
268268
assertThat(branch.isProtected().orElse(false), is(false));
269+
assertTrue(branch.protectionUrl().isEmpty());
270+
assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e"));
271+
}
272+
273+
@Test
274+
public void getBranchWithoutProtectionFields() throws Exception {
275+
final CompletableFuture<Branch> fixture =
276+
completedFuture(json.fromJson(getFixture("branch-no-protection-fields.json"), Branch.class));
277+
when(github.request("/repos/someowner/somerepo/branches/somebranch", Branch.class))
278+
.thenReturn(fixture);
279+
final Branch branch = repoClient.getBranch("somebranch").get();
280+
assertThat(branch.isProtected().orElse(false), is(false));
281+
assertTrue(branch.protectionUrl().isEmpty());
269282
assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e"));
270283
assertThat(
271284
branch.commit().url().toString(),
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+
}

src/test/resources/com/spotify/github/v3/repos/branch-not-protected.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"commit": {
44
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
55
"url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc"
6-
}
6+
},
7+
"protected": false
78
}

0 commit comments

Comments
 (0)