Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Widen CustomProperties type to map[string]interface{} to align with GitHub API #3230

Merged
merged 4 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ Steve Teuber <[email protected]>
Stian Eikeland <[email protected]>
Suhaib Mujahid <[email protected]>
sushmita wable <[email protected]>
Sven Palberg <[email protected]>
Szymon Kodrebski <[email protected]>
Søren Hansen <[email protected]>
T.J. Corrigan <[email protected]>
Expand Down
76 changes: 38 additions & 38 deletions github/event_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1348,44 +1348,44 @@ func (h HeadCommit) String() string {

// PushEventRepository represents the repo object in a PushEvent payload.
type PushEventRepository struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Name *string `json:"name,omitempty"`
FullName *string `json:"full_name,omitempty"`
Owner *User `json:"owner,omitempty"`
Private *bool `json:"private,omitempty"`
Description *string `json:"description,omitempty"`
Fork *bool `json:"fork,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
PushedAt *Timestamp `json:"pushed_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
Homepage *string `json:"homepage,omitempty"`
PullsURL *string `json:"pulls_url,omitempty"`
Size *int `json:"size,omitempty"`
StargazersCount *int `json:"stargazers_count,omitempty"`
WatchersCount *int `json:"watchers_count,omitempty"`
Language *string `json:"language,omitempty"`
HasIssues *bool `json:"has_issues,omitempty"`
HasDownloads *bool `json:"has_downloads,omitempty"`
HasWiki *bool `json:"has_wiki,omitempty"`
HasPages *bool `json:"has_pages,omitempty"`
ForksCount *int `json:"forks_count,omitempty"`
Archived *bool `json:"archived,omitempty"`
Disabled *bool `json:"disabled,omitempty"`
OpenIssuesCount *int `json:"open_issues_count,omitempty"`
DefaultBranch *string `json:"default_branch,omitempty"`
MasterBranch *string `json:"master_branch,omitempty"`
Organization *string `json:"organization,omitempty"`
URL *string `json:"url,omitempty"`
ArchiveURL *string `json:"archive_url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
StatusesURL *string `json:"statuses_url,omitempty"`
GitURL *string `json:"git_url,omitempty"`
SSHURL *string `json:"ssh_url,omitempty"`
CloneURL *string `json:"clone_url,omitempty"`
SVNURL *string `json:"svn_url,omitempty"`
Topics []string `json:"topics,omitempty"`
CustomProperties map[string]string `json:"custom_properties,omitempty"`
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Name *string `json:"name,omitempty"`
FullName *string `json:"full_name,omitempty"`
Owner *User `json:"owner,omitempty"`
Private *bool `json:"private,omitempty"`
Description *string `json:"description,omitempty"`
Fork *bool `json:"fork,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
PushedAt *Timestamp `json:"pushed_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
Homepage *string `json:"homepage,omitempty"`
PullsURL *string `json:"pulls_url,omitempty"`
Size *int `json:"size,omitempty"`
StargazersCount *int `json:"stargazers_count,omitempty"`
WatchersCount *int `json:"watchers_count,omitempty"`
Language *string `json:"language,omitempty"`
HasIssues *bool `json:"has_issues,omitempty"`
HasDownloads *bool `json:"has_downloads,omitempty"`
HasWiki *bool `json:"has_wiki,omitempty"`
HasPages *bool `json:"has_pages,omitempty"`
ForksCount *int `json:"forks_count,omitempty"`
Archived *bool `json:"archived,omitempty"`
Disabled *bool `json:"disabled,omitempty"`
OpenIssuesCount *int `json:"open_issues_count,omitempty"`
DefaultBranch *string `json:"default_branch,omitempty"`
MasterBranch *string `json:"master_branch,omitempty"`
Organization *string `json:"organization,omitempty"`
URL *string `json:"url,omitempty"`
ArchiveURL *string `json:"archive_url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
StatusesURL *string `json:"statuses_url,omitempty"`
GitURL *string `json:"git_url,omitempty"`
SSHURL *string `json:"ssh_url,omitempty"`
CloneURL *string `json:"clone_url,omitempty"`
SVNURL *string `json:"svn_url,omitempty"`
Topics []string `json:"topics,omitempty"`
CustomProperties map[string]interface{} `json:"custom_properties,omitempty"`
}

// PushEventRepoOwner is a basic representation of user/org in a PushEvent payload.
Expand Down
16 changes: 0 additions & 16 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 0 additions & 20 deletions github/github-accessors_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 53 additions & 53 deletions github/repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,59 +27,59 @@ type RepositoriesService service

// Repository represents a GitHub repository.
type Repository struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Owner *User `json:"owner,omitempty"`
Name *string `json:"name,omitempty"`
FullName *string `json:"full_name,omitempty"`
Description *string `json:"description,omitempty"`
Homepage *string `json:"homepage,omitempty"`
CodeOfConduct *CodeOfConduct `json:"code_of_conduct,omitempty"`
DefaultBranch *string `json:"default_branch,omitempty"`
MasterBranch *string `json:"master_branch,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
PushedAt *Timestamp `json:"pushed_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
CloneURL *string `json:"clone_url,omitempty"`
GitURL *string `json:"git_url,omitempty"`
MirrorURL *string `json:"mirror_url,omitempty"`
SSHURL *string `json:"ssh_url,omitempty"`
SVNURL *string `json:"svn_url,omitempty"`
Language *string `json:"language,omitempty"`
Fork *bool `json:"fork,omitempty"`
ForksCount *int `json:"forks_count,omitempty"`
NetworkCount *int `json:"network_count,omitempty"`
OpenIssuesCount *int `json:"open_issues_count,omitempty"`
OpenIssues *int `json:"open_issues,omitempty"` // Deprecated: Replaced by OpenIssuesCount. For backward compatibility OpenIssues is still populated.
StargazersCount *int `json:"stargazers_count,omitempty"`
SubscribersCount *int `json:"subscribers_count,omitempty"`
WatchersCount *int `json:"watchers_count,omitempty"` // Deprecated: Replaced by StargazersCount. For backward compatibility WatchersCount is still populated.
Watchers *int `json:"watchers,omitempty"` // Deprecated: Replaced by StargazersCount. For backward compatibility Watchers is still populated.
Size *int `json:"size,omitempty"`
AutoInit *bool `json:"auto_init,omitempty"`
Parent *Repository `json:"parent,omitempty"`
Source *Repository `json:"source,omitempty"`
TemplateRepository *Repository `json:"template_repository,omitempty"`
Organization *Organization `json:"organization,omitempty"`
Permissions map[string]bool `json:"permissions,omitempty"`
AllowRebaseMerge *bool `json:"allow_rebase_merge,omitempty"`
AllowUpdateBranch *bool `json:"allow_update_branch,omitempty"`
AllowSquashMerge *bool `json:"allow_squash_merge,omitempty"`
AllowMergeCommit *bool `json:"allow_merge_commit,omitempty"`
AllowAutoMerge *bool `json:"allow_auto_merge,omitempty"`
AllowForking *bool `json:"allow_forking,omitempty"`
WebCommitSignoffRequired *bool `json:"web_commit_signoff_required,omitempty"`
DeleteBranchOnMerge *bool `json:"delete_branch_on_merge,omitempty"`
UseSquashPRTitleAsDefault *bool `json:"use_squash_pr_title_as_default,omitempty"`
SquashMergeCommitTitle *string `json:"squash_merge_commit_title,omitempty"` // Can be one of: "PR_TITLE", "COMMIT_OR_PR_TITLE"
SquashMergeCommitMessage *string `json:"squash_merge_commit_message,omitempty"` // Can be one of: "PR_BODY", "COMMIT_MESSAGES", "BLANK"
MergeCommitTitle *string `json:"merge_commit_title,omitempty"` // Can be one of: "PR_TITLE", "MERGE_MESSAGE"
MergeCommitMessage *string `json:"merge_commit_message,omitempty"` // Can be one of: "PR_BODY", "PR_TITLE", "BLANK"
Topics []string `json:"topics,omitempty"`
CustomProperties map[string]string `json:"custom_properties,omitempty"`
Archived *bool `json:"archived,omitempty"`
Disabled *bool `json:"disabled,omitempty"`
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Owner *User `json:"owner,omitempty"`
Name *string `json:"name,omitempty"`
FullName *string `json:"full_name,omitempty"`
Description *string `json:"description,omitempty"`
Homepage *string `json:"homepage,omitempty"`
CodeOfConduct *CodeOfConduct `json:"code_of_conduct,omitempty"`
DefaultBranch *string `json:"default_branch,omitempty"`
MasterBranch *string `json:"master_branch,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
PushedAt *Timestamp `json:"pushed_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
CloneURL *string `json:"clone_url,omitempty"`
GitURL *string `json:"git_url,omitempty"`
MirrorURL *string `json:"mirror_url,omitempty"`
SSHURL *string `json:"ssh_url,omitempty"`
SVNURL *string `json:"svn_url,omitempty"`
Language *string `json:"language,omitempty"`
Fork *bool `json:"fork,omitempty"`
ForksCount *int `json:"forks_count,omitempty"`
NetworkCount *int `json:"network_count,omitempty"`
OpenIssuesCount *int `json:"open_issues_count,omitempty"`
OpenIssues *int `json:"open_issues,omitempty"` // Deprecated: Replaced by OpenIssuesCount. For backward compatibility OpenIssues is still populated.
StargazersCount *int `json:"stargazers_count,omitempty"`
SubscribersCount *int `json:"subscribers_count,omitempty"`
WatchersCount *int `json:"watchers_count,omitempty"` // Deprecated: Replaced by StargazersCount. For backward compatibility WatchersCount is still populated.
Watchers *int `json:"watchers,omitempty"` // Deprecated: Replaced by StargazersCount. For backward compatibility Watchers is still populated.
Size *int `json:"size,omitempty"`
AutoInit *bool `json:"auto_init,omitempty"`
Parent *Repository `json:"parent,omitempty"`
Source *Repository `json:"source,omitempty"`
TemplateRepository *Repository `json:"template_repository,omitempty"`
Organization *Organization `json:"organization,omitempty"`
Permissions map[string]bool `json:"permissions,omitempty"`
AllowRebaseMerge *bool `json:"allow_rebase_merge,omitempty"`
AllowUpdateBranch *bool `json:"allow_update_branch,omitempty"`
AllowSquashMerge *bool `json:"allow_squash_merge,omitempty"`
AllowMergeCommit *bool `json:"allow_merge_commit,omitempty"`
AllowAutoMerge *bool `json:"allow_auto_merge,omitempty"`
AllowForking *bool `json:"allow_forking,omitempty"`
WebCommitSignoffRequired *bool `json:"web_commit_signoff_required,omitempty"`
DeleteBranchOnMerge *bool `json:"delete_branch_on_merge,omitempty"`
UseSquashPRTitleAsDefault *bool `json:"use_squash_pr_title_as_default,omitempty"`
SquashMergeCommitTitle *string `json:"squash_merge_commit_title,omitempty"` // Can be one of: "PR_TITLE", "COMMIT_OR_PR_TITLE"
SquashMergeCommitMessage *string `json:"squash_merge_commit_message,omitempty"` // Can be one of: "PR_BODY", "COMMIT_MESSAGES", "BLANK"
MergeCommitTitle *string `json:"merge_commit_title,omitempty"` // Can be one of: "PR_TITLE", "MERGE_MESSAGE"
MergeCommitMessage *string `json:"merge_commit_message,omitempty"` // Can be one of: "PR_BODY", "PR_TITLE", "BLANK"
Topics []string `json:"topics,omitempty"`
CustomProperties map[string]interface{} `json:"custom_properties,omitempty"`
Archived *bool `json:"archived,omitempty"`
Disabled *bool `json:"disabled,omitempty"`

// Only provided when using RepositoriesService.Get while in preview
License *License `json:"license,omitempty"`
Expand Down
47 changes: 47 additions & 0 deletions github/repos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/url"
"strings"
"testing"
"time"

"github.com/google/go-cmp/cmp"
)
Expand Down Expand Up @@ -4463,3 +4464,49 @@ func TestRepositoriesService_IsPrivateReportingEnabled(t *testing.T) {
return resp, err
})
}

func TestRepository_UnmarshalJSON(t *testing.T) {
var testCases = map[string]struct {
data []byte
wantRepository Repository
wantErr bool
}{
"Empty": {
data: []byte("{}"),
wantRepository: Repository{},
wantErr: false,
},
"Invalid JSON": {
data: []byte("{"),
wantRepository: Repository{},
wantErr: true,
},
"Partial project": {
data: []byte(`{"id":10270722,"name":"go-github","private":false,"owner":{"login":"google"},"created_at":"2013-05-24T16:42:58Z","license":{},"topics":["github"],"permissions":{"pull":true},"custom_properties":{},"organization":{"login":"google"}}`),
wantRepository: Repository{ID: Int64(10270722), Name: String("go-github"), Private: Bool(false), Owner: &User{Login: String("google")}, CreatedAt: &Timestamp{time.Date(2013, 5, 24, 16, 42, 58, 0, time.UTC)}, License: &License{}, Topics: []string{"github"}, Permissions: map[string]bool{"pull": true}, CustomProperties: map[string]interface{}{}, Organization: &Organization{Login: String("google")}},
wantErr: false,
},
"With custom properties": {
data: []byte(`{"custom_properties":{"boolean":"false","text":"a","single-select":"a","multi-select":["a","b","c"]}}`),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this?

Suggested change
data: []byte(`{"custom_properties":{"boolean":"false","text":"a","single-select":"a","multi-select":["a","b","c"]}}`),
data: []byte(`{"custom_properties":{"boolean":false,"int":42,"key":"value","array":["a","b","c"]}}`),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would imply that there is a possible integer value when there is non (that we know of). I used the different custom property types offered by GitHub (see here) and observed their representation in the api, using their label in the github ui as key for this json.

I guess it depends on whats the goal - testing that the unmarshalling works according to the type or testing the possible values that github could send for the given field. What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, well at least please change the boolean from a string to false without the quotes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub returns the value for boolean type custom properties as strings ("true" or "false"). Should I remove the quotes regardless?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? We have hundreds of endpoints that return booleans, and none of them are strings.
What makes you say this? You made a boolean custom property and it is returning a string?!?

Copy link
Contributor Author

@spalberg spalberg Aug 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I configured custom properties of all types and looked at the api response from github.
This is the actual response from the github api with one custom property (named accordingly) of each type configured:

{
  // ...
   "custom_properties": {
      "type-multi-select": [
        "a",
        "b"
      ],
      "type-single-select": "b",
      "type-text": "lala",
      "type-true-false": "true"
  },
  //...
}

Copy link
Contributor Author

@spalberg spalberg Aug 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love it if they would return an actual boolean here 😄

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, bizarre.
Then it looks like there is one linting failure remaining... let me look.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the generated files are out-of-date if you could please update them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole integration of file generation is a really cool golang feature!
Just pushed the updated files 👍

wantRepository: Repository{CustomProperties: map[string]interface{}{"boolean": "false", "text": "a", "single-select": "a", "multi-select": []interface{}{"a", "b", "c"}}},
wantErr: false,
},
}

for name, tt := range testCases {
tt := tt
t.Run(name, func(t *testing.T) {
pk := Repository{}
err := json.Unmarshal(tt.data, &pk)
if err == nil && tt.wantErr {
t.Errorf("Repository.UnmarshalJSON returned nil instead of an error")
}
if err != nil && !tt.wantErr {
t.Errorf("Repository.UnmarshalJSON returned an unexpected error: %+v", err)
}
if !cmp.Equal(tt.wantRepository, pk) {
t.Errorf("Repository.UnmarshalJSON expected repository %+v, got %+v", tt.wantRepository, pk)
}
})
}
}
Loading