diff --git a/github/rules.go b/github/rules.go index d4e94edc269..3699359853a 100644 --- a/github/rules.go +++ b/github/rules.go @@ -8,6 +8,7 @@ package github import ( "encoding/json" "fmt" + "strconv" ) // RulesetTarget represents a GitHub ruleset target. @@ -486,6 +487,37 @@ type RulesetReviewer struct { Type *RulesetReviewerType `json:"type,omitempty"` } +// UnmarshalJSON is a custom JSON unmarshaler for RulesetReviewer. +func (r *RulesetReviewer) UnmarshalJSON(data []byte) error { + var aux struct { + ID any `json:"id,omitempty"` + Type *RulesetReviewerType `json:"type,omitempty"` + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + r.Type = aux.Type + + if aux.ID != nil { + switch id := aux.ID.(type) { + case float64: + r.ID = Ptr(int64(id)) + case string: + i, err := strconv.ParseInt(id, 10, 64) + if err != nil { + return err + } + r.ID = &i + default: + return fmt.Errorf("unexpected type for reviewer.ID: %T", id) + } + } + + return nil +} + // RequiredStatusChecksRuleParameters represents the required status checks rule parameters. type RequiredStatusChecksRuleParameters struct { DoNotEnforceOnCreate *bool `json:"do_not_enforce_on_create,omitempty"` diff --git a/github/rules_test.go b/github/rules_test.go index 05b63fe358c..964ce3cc59b 100644 --- a/github/rules_test.go +++ b/github/rules_test.go @@ -847,6 +847,35 @@ func TestRepositoryRule(t *testing.T) { }, `{"type":"pull_request","parameters":{"allowed_merge_methods":["merge","squash","rebase"],"dismiss_stale_reviews_on_push":false,"require_code_owner_review":false,"require_last_push_approval":false,"required_approving_review_count":0,"required_reviewers":[{"minimum_approvals":1,"file_patterns":["*"],"reviewer":{"id":123456,"type":"Team"}}],"required_review_thread_resolution":false}}`, }, + { + "pull_request_string_id", + &RepositoryRule{ + Type: RulesetRuleTypePullRequest, + Parameters: &PullRequestRuleParameters{ + AllowedMergeMethods: []PullRequestMergeMethod{ + PullRequestMergeMethodMerge, + PullRequestMergeMethodSquash, + PullRequestMergeMethodRebase, + }, + DismissStaleReviewsOnPush: false, + RequireCodeOwnerReview: false, + RequireLastPushApproval: false, + RequiredApprovingReviewCount: 0, + RequiredReviewThreadResolution: false, + RequiredReviewers: []*RulesetRequiredReviewer{ + { + MinimumApprovals: Ptr(1), + FilePatterns: []string{"*"}, + Reviewer: &RulesetReviewer{ + ID: Ptr(int64(123456)), + Type: Ptr(RulesetReviewerTypeTeam), + }, + }, + }, + }, + }, + `{"type":"pull_request","parameters":{"allowed_merge_methods":["merge","squash","rebase"],"dismiss_stale_reviews_on_push":false,"require_code_owner_review":false,"require_last_push_approval":false,"required_approving_review_count":0,"required_reviewers":[{"minimum_approvals":1,"file_patterns":["*"],"reviewer":{"id":"123456","type":"Team"}}],"required_review_thread_resolution":false}}`, + }, { "required_status_checks", &RepositoryRule{ @@ -1172,3 +1201,64 @@ func TestRepositoryRule(t *testing.T) { } }) } + +func TestRulesetReviewer_UnmarshalJSON(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + json string + wantID int64 + wantErr bool + }{ + { + name: "integer_id", + json: `{"id": 123456, "type": "Team"}`, + wantID: 123456, + wantErr: false, + }, + { + name: "string_id", + json: `{"id": "123456", "type": "Team"}`, + wantID: 123456, + wantErr: false, + }, + { + name: "invalid_string_id", + json: `{"id": "not-a-number", "type": "Team"}`, + wantErr: true, + }, + { + name: "invalid_type_id", + json: `{"id": {}, "type": "Team"}`, + wantErr: true, + }, + { + name: "malformed_json", + json: `{"id":`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var got RulesetReviewer + err := json.Unmarshal([]byte(tt.json), &got) + if (err != nil) != tt.wantErr { + t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + if got.GetID() != tt.wantID { + t.Errorf("UnmarshalJSON() got ID = %v, want %v", got.GetID(), tt.wantID) + } + if got.GetType() == nil || *got.GetType() != RulesetReviewerTypeTeam { + t.Errorf("UnmarshalJSON() got Type = %v, want %v", got.GetType(), RulesetReviewerTypeTeam) + } + } + }) + } +}