* $ curl -i https://github.mycompany.com/api/v3/ * HTTP/1.1 401 Unauthorized @@ -955,7 +967,7 @@ public GHNotificationStream listNotifications() { /** * This provides a dump of every public repository, in the order that they were created. - * + * * @see documentation */ public PagedIterablelistAllPublicRepositories() { diff --git a/src/main/java/org/kohsuke/github/example/dataobject/ReadOnlyObjects.java b/src/main/java/org/kohsuke/github/example/dataobject/ReadOnlyObjects.java new file mode 100644 index 0000000000..e56f405979 --- /dev/null +++ b/src/main/java/org/kohsuke/github/example/dataobject/ReadOnlyObjects.java @@ -0,0 +1,475 @@ +package org.kohsuke.github.example.dataobject; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * {@link org.kohsuke.github.GHMeta} wraps the list of GitHub's IP addresses. + * + * This class is used to show examples of different ways to create simple read-only data objects. For data objects that + * can be modified, perform actions, or get other objects we'll need other examples. + *
+ * IMPORTANT: There is no one right way to do this, but there are better and worse. + *
+ *
+ * + * @author Liam Newman + * + * @see org.kohsuke.github.GHMeta + * @see Get Meta + */ + +public final class ReadOnlyObjects { + + /** + * All GHMeta data objects should expose these values. + * + * @author Liam Newman + */ + public interface GHMetaExample { + boolean isVerifiablePasswordAuthentication(); + + List- Better: {@link GHMetaGettersUnmodifiable} is a good balance of clarity and brevity
+ *- Worse: {@link GHMetaPublic} exposes setters that are not needed, making it unclear that fields are actually + * read-only
+ *getHooks(); + + List getGit(); + + List getWeb(); + + List getApi(); + + List getPages(); + + List getImporter(); + } + + /** + * This version uses public getters and setters and leaves it up to Jackson how it wants to fill them. + * + * Pro: + *
+ *
+ * Con: + *- Easy to create
+ *- Not much code
+ *- Mininal annotations
+ *+ *
+ * + * @author Paulo Miguel Almeida + * @see org.kohsuke.github.GHMeta + */ + + public static class GHMetaPublic implements GHMetaExample { + + @JsonProperty("verifiable_password_authentication") + private boolean verifiablePasswordAuthentication; + private List- Exposes public setters for fields that should not be changed
+ *- Lists modifiable when they should not be changed
+ *- Jackson generally doesn't call the setters, it just sets the fields directly
+ *hooks; + private List git; + private List web; + private List api; + private List pages; + private List importer; + + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; + } + + public void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { + this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + } + + public List getHooks() { + return hooks; + } + + public void setHooks(List hooks) { + this.hooks = hooks; + } + + public List getGit() { + return git; + } + + public void setGit(List git) { + this.git = git; + } + + public List getWeb() { + return web; + } + + public void setWeb(List web) { + this.web = web; + } + + public List getApi() { + return api; + } + + public void setApi(List api) { + this.api = api; + } + + public List getPages() { + return pages; + } + + public void setPages(List pages) { + this.pages = pages; + } + + public List getImporter() { + return importer; + } + + public void setImporter(List importer) { + this.importer = importer; + } + + } + + /** + * This version uses public getters and shows that package or private setters both can be used by jackson. You can + * check this by running in debug and setting break points in the setters. + * + * + * Pro: + *
+ *
+ * Con: + *- Easy to create
+ *- Not much code
+ *- Some annotations
+ *+ *
+ * + * @author Liam Newman + * @see org.kohsuke.github.GHMeta + */ + + public static class GHMetaPackage implements GHMetaExample { + + private boolean verifiablePasswordAuthentication; + private List- Exposes some package setters for fields that should not be changed, better than public
+ *- Lists modifiable when they should not be changed
+ *hooks; + private List git; + private List web; + private List api; + private List pages; + + /** + * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. + */ + @JsonProperty + private List importer; + + @JsonProperty("verifiable_password_authentication") + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; + } + + private void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { + this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + } + + @JsonProperty + public List getHooks() { + return hooks; + } + + /** + * Setters can be private (or package local) and will still be called by Jackson. The {@link JsonProperty} can + * got on the getter or setter and still work. + * + * @param hooks + * list of hooks + */ + private void setHooks(List hooks) { + this.hooks = hooks; + } + + public List getGit() { + return git; + } + + /** + * Since we mostly use Jackson for deserialization, {@link JsonSetter} is also okay, but {@link JsonProperty} is + * preferred. + * + * @param git + * list of git addresses + */ + @JsonSetter + void setGit(List git) { + this.git = git; + } + + public List getWeb() { + return web; + } + + /** + * The {@link JsonProperty} can got on the getter or setter and still work. + * + * @param web + * list of web addresses + */ + void setWeb(List web) { + this.web = web; + } + + @JsonProperty + public List getApi() { + return api; + } + + void setApi(List api) { + this.api = api; + } + + @JsonProperty + public List getPages() { + return pages; + } + + void setPages(List pages) { + this.pages = pages; + } + + /** + * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. + * + * @return list of importer addresses + */ + public List getImporter() { + return importer; + } + + /** + * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. + * + * @param importer + * list of importer addresses + */ + void setImporter(List importer) { + this.importer = importer; + } + + } + + /** + * This version uses only public getters and returns unmodifiable lists. + * + * + * + * Pro: + *
+ *
+ * Con: + *- Very Easy to create
+ *- Minimal code
+ *- Mininal annotations
+ *- Fields effectively final and lists unmodifiable
+ *+ *
+ * + * @author Liam Newman + * @see org.kohsuke.github.GHMeta + */ + public static class GHMetaGettersUnmodifiable implements GHMetaExample { + + @JsonProperty("verifiable_password_authentication") + private boolean verifiablePasswordAuthentication; + private List- Effectively final is not quite really final
+ *- If one of the lists were missing (an option member, for example), it will throw NPE but we could mitigate by + * checking for null or assigning a default.
+ *hooks; + private List git; + private List web; + private List api; + private List pages; + /** + * If this were an optional member, we could fill it with an empty list by default. + */ + private List importer = new ArrayList<>(); + + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; + } + + public List getHooks() { + return Collections.unmodifiableList(hooks); + } + + public List getGit() { + return Collections.unmodifiableList(git); + } + + public List getWeb() { + return Collections.unmodifiableList(web); + } + + public List getApi() { + return Collections.unmodifiableList(api); + } + + public List getPages() { + return Collections.unmodifiableList(pages); + } + + public List getImporter() { + return Collections.unmodifiableList(importer); + } + } + + /** + * This version uses only public getters and returns unmodifiable lists and has final fields + * + * Pro: + *
+ *
+ * Con: + *- Moderate amount of code
+ *- More annotations
+ *- Fields final and lists unmodifiable
+ *+ *
+ * + * @author Liam Newman + * @see org.kohsuke.github.GHMeta + */ + public static class GHMetaGettersFinal implements GHMetaExample { + + private final boolean verifiablePasswordAuthentication; + private final List- Extra allocations - default array lists will be replaced by Jackson (yes, even though they are final)
+ *- Added constructor is annoying
+ *- If this object could be refreshed or populated, then the final is misleading (and possibly buggy)
+ *hooks = new ArrayList<>(); + private final List git = new ArrayList<>(); + private final List web = new ArrayList<>(); + private final List api = new ArrayList<>(); + private final List pages = new ArrayList<>(); + private final List importer = new ArrayList<>(); + + @JsonCreator + private GHMetaGettersFinal( + @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { + // boolean fields when final seem to be really final, so we have to switch to constructor + this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + } + + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; + } + + public List getHooks() { + return Collections.unmodifiableList(hooks); + } + + public List getGit() { + return Collections.unmodifiableList(git); + } + + public List getWeb() { + return Collections.unmodifiableList(web); + } + + public List getApi() { + return Collections.unmodifiableList(api); + } + + public List getPages() { + return Collections.unmodifiableList(pages); + } + + public List getImporter() { + return Collections.unmodifiableList(importer); + } + } + + /** + * This version uses only public getters and returns unmodifiable lists + * + * Pro: + *
+ *
+ * Con: + *- Fields final and lists unmodifiable
+ *- Construction behavior can be controlled - if values depended on each other or needed to be set in a specific + * order, this could do that.
+ *+ *
+ * + * @author Liam Newman + * @see org.kohsuke.github.GHMeta + */ + public static class GHMetaGettersFinalCreator implements GHMetaExample { + + private final boolean verifiablePasswordAuthentication; + private final List- There is no way you'd know about this without some research
+ *- Specific annotations needed
+ *- Brittle and verbose - not friendly to optional fields or large number of fields
+ *hooks; + private final List git; + private final List web; + private final List api; + private final List pages; + private final List importer; + + @JsonCreator + private GHMetaGettersFinalCreator(@Nonnull @JsonProperty("hooks") List hooks, + @Nonnull @JsonProperty("git") List git, @Nonnull @JsonProperty("web") List web, + @Nonnull @JsonProperty("api") List api, @Nonnull @JsonProperty("pages") List pages, + @Nonnull @JsonProperty("importer") List importer, + @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { + this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; + this.hooks = Collections.unmodifiableList(hooks); + this.git = Collections.unmodifiableList(git); + this.web = Collections.unmodifiableList(web); + this.api = Collections.unmodifiableList(api); + this.pages = Collections.unmodifiableList(pages); + this.importer = Collections.unmodifiableList(importer); + } + + public boolean isVerifiablePasswordAuthentication() { + return verifiablePasswordAuthentication; + } + + public List getHooks() { + return hooks; + } + + public List getGit() { + return git; + } + + public List getWeb() { + return web; + } + + public List getApi() { + return api; + } + + public List getPages() { + return pages; + } + + public List getImporter() { + return importer; + } + } +} \ No newline at end of file diff --git a/src/test/java/org/kohsuke/github/GitHubTest.java b/src/test/java/org/kohsuke/github/GitHubTest.java index 41e6ef9830..81cfeba27b 100644 --- a/src/test/java/org/kohsuke/github/GitHubTest.java +++ b/src/test/java/org/kohsuke/github/GitHubTest.java @@ -1,18 +1,13 @@ package org.kohsuke.github; -import java.io.FileNotFoundException; import java.io.IOException; -import java.lang.reflect.Field; import java.util.*; import com.google.common.collect.Iterables; -import org.apache.commons.io.IOUtils; -import org.junit.Ignore; import org.junit.Test; +import org.kohsuke.github.example.dataobject.ReadOnlyObjects; import static org.hamcrest.CoreMatchers.*; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; /** * Unit test for {@link GitHub}. @@ -79,4 +74,33 @@ public void testListMyAuthorizations() throws IOException { assertNotNull(auth.getAppName()); } } + + @Test + public void getMeta() throws IOException { + GHMeta meta = gitHub.getMeta(); + assertTrue(meta.isVerifiablePasswordAuthentication()); + assertEquals(19, meta.getApi().size()); + assertEquals(19, meta.getGit().size()); + assertEquals(3, meta.getHooks().size()); + assertEquals(6, meta.getImporter().size()); + assertEquals(6, meta.getPages().size()); + assertEquals(19, meta.getWeb().size()); + + // Also test examples here + Class[] examples = new Class[] { ReadOnlyObjects.GHMetaPublic.class, ReadOnlyObjects.GHMetaPackage.class, + ReadOnlyObjects.GHMetaGettersUnmodifiable.class, ReadOnlyObjects.GHMetaGettersFinal.class, + ReadOnlyObjects.GHMetaGettersFinalCreator.class, }; + + for (Class metaClass : examples) { + ReadOnlyObjects.GHMetaExample metaExample = gitHub.retrieve().to("/meta", + (Class ) metaClass); + assertTrue(metaExample.isVerifiablePasswordAuthentication()); + assertEquals(19, metaExample.getApi().size()); + assertEquals(19, metaExample.getGit().size()); + assertEquals(3, metaExample.getHooks().size()); + assertEquals(6, metaExample.getImporter().size()); + assertEquals(6, metaExample.getPages().size()); + assertEquals(19, metaExample.getWeb().size()); + } + } } diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/__files/meta-d7546658-0019-4aac-ad1a-502d7abd8050.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/__files/meta-d7546658-0019-4aac-ad1a-502d7abd8050.json new file mode 100644 index 0000000000..02db520957 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/__files/meta-d7546658-0019-4aac-ad1a-502d7abd8050.json @@ -0,0 +1,87 @@ +{ + "verifiable_password_authentication": true, + "hooks": [ + "192.30.252.0/22", + "185.199.108.0/22", + "140.82.112.0/20" + ], + "web": [ + "192.30.252.0/22", + "185.199.108.0/22", + "140.82.112.0/20", + "13.114.40.48/32", + "13.229.188.59/32", + "13.234.176.102/32", + "13.234.210.38/32", + "13.236.229.21/32", + "13.237.44.5/32", + "13.250.177.223/32", + "15.164.81.167/32", + "18.194.104.89/32", + "18.195.85.27/32", + "35.159.8.160/32", + "52.192.72.89/32", + "52.64.108.95/32", + "52.69.186.44/32", + "52.74.223.119/32", + "52.78.231.108/32" + ], + "api": [ + "192.30.252.0/22", + "185.199.108.0/22", + "140.82.112.0/20", + "13.209.163.61/32", + "13.230.158.120/32", + "13.233.76.15/32", + "13.234.168.60/32", + "13.236.14.80/32", + "13.238.54.232/32", + "13.250.168.23/32", + "13.250.94.254/32", + "18.179.245.253/32", + "18.194.201.191/32", + "18.195.135.122/32", + "52.58.203.252/32", + "52.63.231.178/32", + "52.69.239.207/32", + "54.169.195.247/32", + "54.180.75.25/32" + ], + "git": [ + "192.30.252.0/22", + "185.199.108.0/22", + "140.82.112.0/20", + "13.114.40.48/32", + "13.229.188.59/32", + "13.234.176.102/32", + "13.234.210.38/32", + "13.236.229.21/32", + "13.237.44.5/32", + "13.250.177.223/32", + "15.164.81.167/32", + "18.194.104.89/32", + "18.195.85.27/32", + "35.159.8.160/32", + "52.192.72.89/32", + "52.64.108.95/32", + "52.69.186.44/32", + "52.74.223.119/32", + "52.78.231.108/32" + ], + "pages": [ + "192.30.252.153/32", + "192.30.252.154/32", + "185.199.108.153/32", + "185.199.109.153/32", + "185.199.110.153/32", + "185.199.111.153/32" + ], + "importer": [ + "54.87.5.173", + "54.166.52.62", + "23.20.92.3", + "192.30.252.0/22", + "185.199.108.0/22", + "140.82.112.0/20" + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/mappings/meta-1-d75466.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/mappings/meta-1-d75466.json new file mode 100644 index 0000000000..37c886c2cc --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/getMeta/mappings/meta-1-d75466.json @@ -0,0 +1,40 @@ +{ + "id": "d7546658-0019-4aac-ad1a-502d7abd8050", + "name": "meta", + "request": { + "url": "/meta", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "meta-d7546658-0019-4aac-ad1a-502d7abd8050.json", + "headers": { + "Date": "Thu, 14 Nov 2019 02:46:14 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "60", + "X-RateLimit-Remaining": "59", + "X-RateLimit-Reset": "1573703174", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": [ + "Accept", + "Accept-Encoding" + ], + "ETag": "W/\"eb00a8c05920e4cb21eb0d018c339fe1\"", + "X-GitHub-Media-Type": "unknown, github.v3", + "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", + "Access-Control-Allow-Origin": "*", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "FC2E:3B7E:AD9E9:C9032:5DCCBFF6" + } + }, + "uuid": "d7546658-0019-4aac-ad1a-502d7abd8050", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file