Expose MappingReader and MappingWriter

Jenkins Blue Ocean made interesting design choices relating github-api interactions.

They mostly reused the existing API and OM, but in a few places they chose to
implement their own object mapping independent of this project. This is fine
as long as nothing in this project ever changes, including internals such
as ObjectMapper configuration or behavior.

Recent release have made changes to those internals which break assumptions made
in Blue Ocean.

This change exposes this project's MappingReader and MappingWriter to allow
for a fix to Blue Ocean requiring only minimal changes.

This doesn't prevent future changes from breaking Blue Ocean but at least makes
them much less likely.

Fixes #780
This commit is contained in:
Liam Newman
2020-04-14 17:26:04 -07:00
parent 76c51922f1
commit e0aee9f361
10 changed files with 415 additions and 4 deletions

View File

@@ -24,7 +24,7 @@ public abstract class GHObject {
/**
* Capture response HTTP headers on the state object.
*/
protected Map<String, List<String>> responseHeaderFields;
protected transient Map<String, List<String>> responseHeaderFields;
protected String url;
protected long id;

View File

@@ -63,7 +63,7 @@ import static org.kohsuke.github.Previews.*;
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" },
justification = "JSON API")
public class GHRepository extends GHObject {
/* package almost final */ GitHub root;
/* package almost final */ transient GitHub root;
private String nodeId, description, homepage, name, full_name;
private String html_url; // this is the UI

View File

@@ -23,6 +23,8 @@
*/
package org.kohsuke.github;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.*;
@@ -1190,6 +1192,32 @@ public class GitHub {
"UTF-8");
}
/**
* Do not use this method. This method will be removed and should never have been needed in the first place.
*
* @return an {@link ObjectWriter} instance that can be further configured.
* @deprecated DO NOT USE THIS METHOD. Provided for backward compatibility with projects that did their own jackson
* mapping of this project's data objects, such as Jenkins Blue Ocean.
*/
@Deprecated
@Nonnull
public static ObjectWriter getMappingObjectWriter() {
return GitHubClient.getMappingObjectWriter();
}
/**
* Do not use this method. This method will be removed and should never have been needed in the first place.
*
* @return an {@link ObjectReader} instance that can be further configured.
* @deprecated DO NOT USE THIS METHOD. Provided for backward compatibility with projects that did their own jackson
* mapping of this project's data objects, such as Jenkins Blue Ocean.
*/
@Deprecated
@Nonnull
public static ObjectReader getMappingObjectReader() {
return GitHubClient.getMappingObjectReader(GitHub.offline());
}
@Nonnull
GitHubClient getClient() {
return client;

View File

@@ -8,8 +8,7 @@ import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.TimeZone;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.core.Is.is;
/**
@@ -131,6 +130,32 @@ public class GitHubStaticTest extends AbstractGitHubWireMockTest {
}
@Test
public void testMappingReaderWriter() throws Exception {
// This test ensures that data objects can be written and read in a raw form from string.
// This behavior is completely unsupported and should not be used but given that some
// clients, such as Jenkins Blue Ocean, have already implemented their own Jackson
// Reader and Writer that bind this library's data objects from outside this library
// this makes sure they don't break.
GHRepository repo = getTempRepository();
assertThat(repo.root, not(nullValue()));
String repoString = GitHub.getMappingObjectWriter().writeValueAsString(repo);
assertThat(repoString, not(nullValue()));
assertThat(repoString, containsString("testMappingReaderWriter"));
GHRepository readRepo = GitHub.getMappingObjectReader().forType(GHRepository.class).readValue(repoString);
// This should never happen if these methods aren't used
assertThat(readRepo.root, nullValue());
String readRepoString = GitHub.getMappingObjectWriter().writeValueAsString(readRepo);
assertThat(readRepoString, equalTo(repoString));
}
static String formatDate(Date dt, String format) {
SimpleDateFormat df = new SimpleDateFormat(format);
df.setTimeZone(TimeZone.getTimeZone("GMT"));

View File

@@ -0,0 +1,126 @@
{
"id": 256061594,
"node_id": "MDEwOlJlcG9zaXRvcnkyNTYwNjE1OTQ=",
"name": "temp-testMappingReaderWriter",
"full_name": "github-api-test-org/temp-testMappingReaderWriter",
"private": false,
"owner": {
"login": "github-api-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/github-api-test-org",
"html_url": "https://github.com/github-api-test-org",
"followers_url": "https://api.github.com/users/github-api-test-org/followers",
"following_url": "https://api.github.com/users/github-api-test-org/following{/other_user}",
"gists_url": "https://api.github.com/users/github-api-test-org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/github-api-test-org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/github-api-test-org/subscriptions",
"organizations_url": "https://api.github.com/users/github-api-test-org/orgs",
"repos_url": "https://api.github.com/users/github-api-test-org/repos",
"events_url": "https://api.github.com/users/github-api-test-org/events{/privacy}",
"received_events_url": "https://api.github.com/users/github-api-test-org/received_events",
"type": "Organization",
"site_admin": false
},
"html_url": "https://github.com/github-api-test-org/temp-testMappingReaderWriter",
"description": "A test repository for testing the github-api project: temp-testMappingReaderWriter",
"fork": false,
"url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter",
"forks_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/forks",
"keys_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/teams",
"hooks_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/hooks",
"issue_events_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/issues/events{/number}",
"events_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/events",
"assignees_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/assignees{/user}",
"branches_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/branches{/branch}",
"tags_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/tags",
"blobs_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/statuses/{sha}",
"languages_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/languages",
"stargazers_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/stargazers",
"contributors_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/contributors",
"subscribers_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/subscribers",
"subscription_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/subscription",
"commits_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/contents/{+path}",
"compare_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/merges",
"archive_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/downloads",
"issues_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/issues{/number}",
"pulls_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/pulls{/number}",
"milestones_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/milestones{/number}",
"notifications_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/labels{/name}",
"releases_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/releases{/id}",
"deployments_url": "https://api.github.com/repos/github-api-test-org/temp-testMappingReaderWriter/deployments",
"created_at": "2020-04-15T23:38:53Z",
"updated_at": "2020-04-15T23:38:58Z",
"pushed_at": "2020-04-15T23:38:55Z",
"git_url": "git://github.com/github-api-test-org/temp-testMappingReaderWriter.git",
"ssh_url": "git@github.com:github-api-test-org/temp-testMappingReaderWriter.git",
"clone_url": "https://github.com/github-api-test-org/temp-testMappingReaderWriter.git",
"svn_url": "https://github.com/github-api-test-org/temp-testMappingReaderWriter",
"homepage": "http://github-api.kohsuke.org/",
"size": 0,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 0,
"license": null,
"forks": 0,
"open_issues": 0,
"watchers": 0,
"default_branch": "master",
"permissions": {
"admin": true,
"push": true,
"pull": true
},
"temp_clone_token": "",
"allow_squash_merge": true,
"allow_merge_commit": true,
"allow_rebase_merge": true,
"delete_branch_on_merge": false,
"organization": {
"login": "github-api-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/github-api-test-org",
"html_url": "https://github.com/github-api-test-org",
"followers_url": "https://api.github.com/users/github-api-test-org/followers",
"following_url": "https://api.github.com/users/github-api-test-org/following{/other_user}",
"gists_url": "https://api.github.com/users/github-api-test-org/gists{/gist_id}",
"starred_url": "https://api.github.com/users/github-api-test-org/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/github-api-test-org/subscriptions",
"organizations_url": "https://api.github.com/users/github-api-test-org/orgs",
"repos_url": "https://api.github.com/users/github-api-test-org/repos",
"events_url": "https://api.github.com/users/github-api-test-org/events{/privacy}",
"received_events_url": "https://api.github.com/users/github-api-test-org/received_events",
"type": "Organization",
"site_admin": false
},
"network_count": 0,
"subscribers_count": 7
}

View File

@@ -0,0 +1,45 @@
{
"login": "bitwiseman",
"id": 1958953,
"node_id": "MDQ6VXNlcjE5NTg5NTM=",
"avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/bitwiseman",
"html_url": "https://github.com/bitwiseman",
"followers_url": "https://api.github.com/users/bitwiseman/followers",
"following_url": "https://api.github.com/users/bitwiseman/following{/other_user}",
"gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}",
"starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions",
"organizations_url": "https://api.github.com/users/bitwiseman/orgs",
"repos_url": "https://api.github.com/users/bitwiseman/repos",
"events_url": "https://api.github.com/users/bitwiseman/events{/privacy}",
"received_events_url": "https://api.github.com/users/bitwiseman/received_events",
"type": "User",
"site_admin": false,
"name": "Liam Newman",
"company": "Cloudbees, Inc.",
"blog": "",
"location": "Seattle, WA, USA",
"email": "bitwiseman@gmail.com",
"hireable": null,
"bio": "https://twitter.com/bitwiseman",
"public_repos": 181,
"public_gists": 7,
"followers": 156,
"following": 9,
"created_at": "2012-07-11T20:38:33Z",
"updated_at": "2020-04-14T20:00:03Z",
"private_gists": 8,
"total_private_repos": 10,
"owned_private_repos": 0,
"disk_usage": 33697,
"collaborators": 0,
"two_factor_authentication": true,
"plan": {
"name": "free",
"space": 976562499,
"collaborators": 0,
"private_repos": 10000
}
}

View File

@@ -0,0 +1,46 @@
{
"id": "de382884-bcbf-4c2e-9ae0-176f926dcb0d",
"name": "repos_github-api-test-org_temp-testmappingreaderwriter",
"request": {
"url": "/repos/github-api-test-org/temp-testMappingReaderWriter",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "repos_github-api-test-org_temp-testmappingreaderwriter-2.json",
"headers": {
"Date": "Wed, 15 Apr 2020 23:38:59 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4948",
"X-RateLimit-Reset": "1586996337",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "W/\"f24612ba790ffe0f1925c048a05e0f76\"",
"Last-Modified": "Wed, 15 Apr 2020 23:38:58 GMT",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "repo",
"X-GitHub-Media-Type": "unknown, github.v3",
"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": "F3AE:08E3:30A82:3B66F:5E979B0C"
}
},
"uuid": "de382884-bcbf-4c2e-9ae0-176f926dcb0d",
"persistent": true,
"insertionIndex": 2
}

View File

@@ -0,0 +1,48 @@
{
"id": "ab96e191-face-4ca5-a7a2-73b552c74559",
"name": "repos_github-api-test-org_temp-testmappingreaderwriter_hooks",
"request": {
"url": "/repos/github-api-test-org/temp-testMappingReaderWriter/hooks",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"body": "[]",
"headers": {
"Date": "Wed, 15 Apr 2020 23:38:59 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4947",
"X-RateLimit-Reset": "1586996337",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "\"c2ec59aeeea67fff8edf681155a22565\"",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "admin:repo_hook, public_repo, read:repo_hook, repo, write:repo_hook",
"X-GitHub-Media-Type": "unknown, github.v3",
"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": "F3AE:08E3:30A85:3B709:5E979B13"
}
},
"uuid": "ab96e191-face-4ca5-a7a2-73b552c74559",
"persistent": true,
"scenarioName": "scenario-1-repos-github-api-test-org-temp-testMappingReaderWriter-hooks",
"requiredScenarioState": "Started",
"newScenarioState": "scenario-1-repos-github-api-test-org-temp-testMappingReaderWriter-hooks-2",
"insertionIndex": 3
}

View File

@@ -0,0 +1,47 @@
{
"id": "d823b1ff-b674-4367-9d0e-7ac896c8e031",
"name": "repos_github-api-test-org_temp-testmappingreaderwriter_hooks",
"request": {
"url": "/repos/github-api-test-org/temp-testMappingReaderWriter/hooks",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"body": "[]",
"headers": {
"Date": "Wed, 15 Apr 2020 23:38:59 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4946",
"X-RateLimit-Reset": "1586996336",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "\"c2ec59aeeea67fff8edf681155a22565\"",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "admin:repo_hook, public_repo, read:repo_hook, repo, write:repo_hook",
"X-GitHub-Media-Type": "unknown, github.v3",
"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": "F3AE:08E3:30A88:3B70E:5E979B13"
}
},
"uuid": "d823b1ff-b674-4367-9d0e-7ac896c8e031",
"persistent": true,
"scenarioName": "scenario-1-repos-github-api-test-org-temp-testMappingReaderWriter-hooks",
"requiredScenarioState": "scenario-1-repos-github-api-test-org-temp-testMappingReaderWriter-hooks-2",
"insertionIndex": 4
}

View File

@@ -0,0 +1,46 @@
{
"id": "71089e3f-b552-4970-b83c-d5789b14007b",
"name": "user",
"request": {
"url": "/user",
"method": "GET",
"headers": {
"Accept": {
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
}
}
},
"response": {
"status": 200,
"bodyFileName": "user-1.json",
"headers": {
"Date": "Wed, 15 Apr 2020 23:38:52 GMT",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4953",
"X-RateLimit-Reset": "1586996337",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding, Accept, X-Requested-With"
],
"ETag": "W/\"1b9c035d6effae6b189e5afe6994d306\"",
"Last-Modified": "Tue, 14 Apr 2020 20:00:03 GMT",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"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": "F3AE:08E3:30A06:3B66C:5E979B0C"
}
},
"uuid": "71089e3f-b552-4970-b83c-d5789b14007b",
"persistent": true,
"insertionIndex": 1
}