From 55eed4087299fdf49dd3728e4671dda4305ac5e1 Mon Sep 17 00:00:00 2001 From: JamBalaya56562 Date: Wed, 22 Jul 2026 20:10:12 +0900 Subject: [PATCH 1/2] refactor!: Split `CreateOrUpdateCustomRepoRoleOptions` into `CreateCustomRepoRoleRequest` and `UpdateCustomRepoRoleRequest` and pass by value The create and update endpoints have different required fields, so the shared options type is split into two request types. On create, name, base_role and permissions are required, so `Name` and `BaseRole` become non-pointer strings without omitempty. On update every field is optional, so `UpdateCustomRepoRoleRequest` keeps them all as pointers. Both `OrganizationsService.CreateCustomRepoRole` and `UpdateCustomRepoRole` now take their request body by value, and the old type is removed from the .golangci.yml allowlists. BREAKING CHANGE: CreateOrUpdateCustomRepoRoleOptions is split into CreateCustomRepoRoleRequest (with non-pointer Name and BaseRole) and UpdateCustomRepoRoleRequest; OrganizationsService.CreateCustomRepoRole and UpdateCustomRepoRole now take these request types by value. --- .golangci.yml | 2 - github/github-accessors.go | 96 ++++++++++----- github/github-accessors_test.go | 126 +++++++++++++------- github/orgs_custom_repository_roles.go | 18 ++- github/orgs_custom_repository_roles_test.go | 20 ++-- 5 files changed, 169 insertions(+), 93 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index dc5daf2364d..0dcecffc084 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -211,7 +211,6 @@ linters: - ConfigApplyOptions - ConfigSettings - CreateCodespaceOptions - - CreateOrUpdateCustomRepoRoleOptions - CreateOrUpdateIssueTypesOptions - CreateOrgInvitationOptions - CreateUpdateEnvironment @@ -281,7 +280,6 @@ linters: - CreateCheckRunOptions - CreateCheckSuiteOptions - CreateCodespaceOptions - - CreateOrUpdateCustomRepoRoleOptions - CreateOrUpdateIssueTypesOptions - CreateOrgInvitationOptions - ImpersonateUserOptions diff --git a/github/github-accessors.go b/github/github-accessors.go index c703fb98d98..d1ca3d3c609 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -10918,6 +10918,38 @@ func (c *CreateCustomOrgRoleRequest) GetPermissions() []string { return c.Permissions } +// GetBaseRole returns the BaseRole field. +func (c *CreateCustomRepoRoleRequest) GetBaseRole() string { + if c == nil { + return "" + } + return c.BaseRole +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CreateCustomRepoRoleRequest) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + +// GetName returns the Name field. +func (c *CreateCustomRepoRoleRequest) GetName() string { + if c == nil { + return "" + } + return c.Name +} + +// GetPermissions returns the Permissions slice if it's non-nil, nil otherwise. +func (c *CreateCustomRepoRoleRequest) GetPermissions() []string { + if c == nil || c.Permissions == nil { + return nil + } + return c.Permissions +} + // GetName returns the Name field. func (c *CreateDeploymentBranchPolicyRequest) GetName() string { if c == nil { @@ -11438,38 +11470,6 @@ func (c *CreateOrgInvitationOptions) GetTeamID() []int64 { return c.TeamID } -// GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise. -func (c *CreateOrUpdateCustomRepoRoleOptions) GetBaseRole() string { - if c == nil || c.BaseRole == nil { - return "" - } - return *c.BaseRole -} - -// GetDescription returns the Description field if it's non-nil, zero value otherwise. -func (c *CreateOrUpdateCustomRepoRoleOptions) GetDescription() string { - if c == nil || c.Description == nil { - return "" - } - return *c.Description -} - -// GetName returns the Name field if it's non-nil, zero value otherwise. -func (c *CreateOrUpdateCustomRepoRoleOptions) GetName() string { - if c == nil || c.Name == nil { - return "" - } - return *c.Name -} - -// GetPermissions returns the Permissions slice if it's non-nil, nil otherwise. -func (c *CreateOrUpdateCustomRepoRoleOptions) GetPermissions() []string { - if c == nil || c.Permissions == nil { - return nil - } - return c.Permissions -} - // GetColor returns the Color field if it's non-nil, zero value otherwise. func (c *CreateOrUpdateIssueTypesOptions) GetColor() string { if c == nil || c.Color == nil { @@ -42630,6 +42630,38 @@ func (u *UpdateCustomOrgRoleRequest) GetPermissions() []string { return u.Permissions } +// GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise. +func (u *UpdateCustomRepoRoleRequest) GetBaseRole() string { + if u == nil || u.BaseRole == nil { + return "" + } + return *u.BaseRole +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (u *UpdateCustomRepoRoleRequest) GetDescription() string { + if u == nil || u.Description == nil { + return "" + } + return *u.Description +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (u *UpdateCustomRepoRoleRequest) GetName() string { + if u == nil || u.Name == nil { + return "" + } + return *u.Name +} + +// GetPermissions returns the Permissions slice if it's non-nil, nil otherwise. +func (u *UpdateCustomRepoRoleRequest) GetPermissions() []string { + if u == nil || u.Permissions == nil { + return nil + } + return u.Permissions +} + // GetLanguages returns the Languages slice if it's non-nil, nil otherwise. func (u *UpdateDefaultSetupConfigurationOptions) GetLanguages() []string { if u == nil || u.Languages == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 38cfcbb866e..1bd9940308b 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -13847,6 +13847,44 @@ func TestCreateCustomOrgRoleRequest_GetPermissions(tt *testing.T) { c.GetPermissions() } +func TestCreateCustomRepoRoleRequest_GetBaseRole(tt *testing.T) { + tt.Parallel() + c := &CreateCustomRepoRoleRequest{} + c.GetBaseRole() + c = nil + c.GetBaseRole() +} + +func TestCreateCustomRepoRoleRequest_GetDescription(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateCustomRepoRoleRequest{Description: &zeroValue} + c.GetDescription() + c = &CreateCustomRepoRoleRequest{} + c.GetDescription() + c = nil + c.GetDescription() +} + +func TestCreateCustomRepoRoleRequest_GetName(tt *testing.T) { + tt.Parallel() + c := &CreateCustomRepoRoleRequest{} + c.GetName() + c = nil + c.GetName() +} + +func TestCreateCustomRepoRoleRequest_GetPermissions(tt *testing.T) { + tt.Parallel() + zeroValue := []string{} + c := &CreateCustomRepoRoleRequest{Permissions: zeroValue} + c.GetPermissions() + c = &CreateCustomRepoRoleRequest{} + c.GetPermissions() + c = nil + c.GetPermissions() +} + func TestCreateDeploymentBranchPolicyRequest_GetName(tt *testing.T) { tt.Parallel() c := &CreateDeploymentBranchPolicyRequest{} @@ -14511,50 +14549,6 @@ func TestCreateOrgInvitationOptions_GetTeamID(tt *testing.T) { c.GetTeamID() } -func TestCreateOrUpdateCustomRepoRoleOptions_GetBaseRole(tt *testing.T) { - tt.Parallel() - var zeroValue string - c := &CreateOrUpdateCustomRepoRoleOptions{BaseRole: &zeroValue} - c.GetBaseRole() - c = &CreateOrUpdateCustomRepoRoleOptions{} - c.GetBaseRole() - c = nil - c.GetBaseRole() -} - -func TestCreateOrUpdateCustomRepoRoleOptions_GetDescription(tt *testing.T) { - tt.Parallel() - var zeroValue string - c := &CreateOrUpdateCustomRepoRoleOptions{Description: &zeroValue} - c.GetDescription() - c = &CreateOrUpdateCustomRepoRoleOptions{} - c.GetDescription() - c = nil - c.GetDescription() -} - -func TestCreateOrUpdateCustomRepoRoleOptions_GetName(tt *testing.T) { - tt.Parallel() - var zeroValue string - c := &CreateOrUpdateCustomRepoRoleOptions{Name: &zeroValue} - c.GetName() - c = &CreateOrUpdateCustomRepoRoleOptions{} - c.GetName() - c = nil - c.GetName() -} - -func TestCreateOrUpdateCustomRepoRoleOptions_GetPermissions(tt *testing.T) { - tt.Parallel() - zeroValue := []string{} - c := &CreateOrUpdateCustomRepoRoleOptions{Permissions: zeroValue} - c.GetPermissions() - c = &CreateOrUpdateCustomRepoRoleOptions{} - c.GetPermissions() - c = nil - c.GetPermissions() -} - func TestCreateOrUpdateIssueTypesOptions_GetColor(tt *testing.T) { tt.Parallel() var zeroValue string @@ -53416,6 +53410,50 @@ func TestUpdateCustomOrgRoleRequest_GetPermissions(tt *testing.T) { u.GetPermissions() } +func TestUpdateCustomRepoRoleRequest_GetBaseRole(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateCustomRepoRoleRequest{BaseRole: &zeroValue} + u.GetBaseRole() + u = &UpdateCustomRepoRoleRequest{} + u.GetBaseRole() + u = nil + u.GetBaseRole() +} + +func TestUpdateCustomRepoRoleRequest_GetDescription(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateCustomRepoRoleRequest{Description: &zeroValue} + u.GetDescription() + u = &UpdateCustomRepoRoleRequest{} + u.GetDescription() + u = nil + u.GetDescription() +} + +func TestUpdateCustomRepoRoleRequest_GetName(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateCustomRepoRoleRequest{Name: &zeroValue} + u.GetName() + u = &UpdateCustomRepoRoleRequest{} + u.GetName() + u = nil + u.GetName() +} + +func TestUpdateCustomRepoRoleRequest_GetPermissions(tt *testing.T) { + tt.Parallel() + zeroValue := []string{} + u := &UpdateCustomRepoRoleRequest{Permissions: zeroValue} + u.GetPermissions() + u = &UpdateCustomRepoRoleRequest{} + u.GetPermissions() + u = nil + u.GetPermissions() +} + func TestUpdateDefaultSetupConfigurationOptions_GetLanguages(tt *testing.T) { tt.Parallel() zeroValue := []string{} diff --git a/github/orgs_custom_repository_roles.go b/github/orgs_custom_repository_roles.go index 7903fb22553..8d037e3f488 100644 --- a/github/orgs_custom_repository_roles.go +++ b/github/orgs_custom_repository_roles.go @@ -30,12 +30,20 @@ type CustomRepoRoles struct { UpdatedAt *Timestamp `json:"updated_at,omitempty"` } -// CreateOrUpdateCustomRepoRoleOptions represents options required to create or update a custom repository role. -type CreateOrUpdateCustomRepoRoleOptions struct { +// CreateCustomRepoRoleRequest represents the parameters to create a custom repository role. +type CreateCustomRepoRoleRequest struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` + BaseRole string `json:"base_role"` + Permissions []string `json:"permissions"` +} + +// UpdateCustomRepoRoleRequest represents the parameters to update a custom repository role. +type UpdateCustomRepoRoleRequest struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` BaseRole *string `json:"base_role,omitempty"` - Permissions []string `json:"permissions"` + Permissions []string `json:"permissions,omitempty"` } // RepoFineGrainedPermission represents a fine-grained permission that can be used in a custom repository role. @@ -96,7 +104,7 @@ func (s *OrganizationsService) GetCustomRepoRole(ctx context.Context, org string // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles?apiVersion=2022-11-28#create-a-custom-repository-role // //meta:operation POST /orgs/{org}/custom-repository-roles -func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org string, body *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) { +func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org string, body CreateCustomRepoRoleRequest) (*CustomRepoRoles, *Response, error) { u := fmt.Sprintf("orgs/%v/custom-repository-roles", org) req, err := s.client.NewRequest(ctx, "POST", u, body) @@ -119,7 +127,7 @@ func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org str // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles?apiVersion=2022-11-28#update-a-custom-repository-role // //meta:operation PATCH /orgs/{org}/custom-repository-roles/{role_id} -func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org string, roleID int64, body *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) { +func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org string, roleID int64, body UpdateCustomRepoRoleRequest) (*CustomRepoRoles, *Response, error) { u := fmt.Sprintf("orgs/%v/custom-repository-roles/%v", org, roleID) req, err := s.client.NewRequest(ctx, "PATCH", u, body) diff --git a/github/orgs_custom_repository_roles_test.go b/github/orgs_custom_repository_roles_test.go index 38a2e5b4182..4a980a4809e 100644 --- a/github/orgs_custom_repository_roles_test.go +++ b/github/orgs_custom_repository_roles_test.go @@ -183,13 +183,13 @@ func TestOrganizationsService_CreateCustomRepoRole(t *testing.T) { ctx := t.Context() - opts := &CreateOrUpdateCustomRepoRoleOptions{ - Name: Ptr("Labeler"), + body := CreateCustomRepoRoleRequest{ + Name: "Labeler", Description: Ptr("A role for issue and PR labelers"), - BaseRole: Ptr("read"), + BaseRole: "read", Permissions: []string{"add_label"}, } - apps, _, err := client.Organizations.CreateCustomRepoRole(ctx, "o", opts) + apps, _, err := client.Organizations.CreateCustomRepoRole(ctx, "o", body) if err != nil { t.Errorf("Organizations.CreateCustomRepoRole returned error: %v", err) } @@ -202,12 +202,12 @@ func TestOrganizationsService_CreateCustomRepoRole(t *testing.T) { const methodName = "CreateCustomRepoRole" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Organizations.CreateCustomRepoRole(ctx, "\no", nil) + _, _, err = client.Organizations.CreateCustomRepoRole(ctx, "\no", CreateCustomRepoRoleRequest{}) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Organizations.CreateCustomRepoRole(ctx, "o", nil) + got, resp, err := client.Organizations.CreateCustomRepoRole(ctx, "o", CreateCustomRepoRoleRequest{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -226,11 +226,11 @@ func TestOrganizationsService_UpdateCustomRepoRole(t *testing.T) { ctx := t.Context() - opts := &CreateOrUpdateCustomRepoRoleOptions{ + body := UpdateCustomRepoRoleRequest{ Name: Ptr("Updated Name"), Description: Ptr("Updated Description"), } - apps, _, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, opts) + apps, _, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, body) if err != nil { t.Errorf("Organizations.UpdateCustomRepoRole returned error: %v", err) } @@ -243,12 +243,12 @@ func TestOrganizationsService_UpdateCustomRepoRole(t *testing.T) { const methodName = "UpdateCustomRepoRole" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Organizations.UpdateCustomRepoRole(ctx, "\no", 8030, nil) + _, _, err = client.Organizations.UpdateCustomRepoRole(ctx, "\no", 8030, UpdateCustomRepoRoleRequest{}) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, nil) + got, resp, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, UpdateCustomRepoRoleRequest{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } From db0c11e2a4ca407d899ffb93e2123ff956ae2f49 Mon Sep 17 00:00:00 2001 From: JamBalaya56562 Date: Thu, 23 Jul 2026 04:36:48 +0900 Subject: [PATCH 2/2] refactor: Use `omitzero` for `UpdateCustomRepoRoleRequest.Permissions` With `omitempty` an empty slice is indistinguishable from a nil one, so callers could not send an explicit empty array to clear the additional permissions on a role. `omitzero` only omits the nil slice, so a nil value leaves the permissions untouched while `[]string{}` sends `"permissions": []`. This matches the existing use of `omitzero` for slices where an empty array is meaningful, such as `ActionsAllowed.PatternsAllowed` and `DeploymentRequest.RequiredContexts`. --- github/orgs_custom_repository_roles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/orgs_custom_repository_roles.go b/github/orgs_custom_repository_roles.go index 8d037e3f488..c98785398cc 100644 --- a/github/orgs_custom_repository_roles.go +++ b/github/orgs_custom_repository_roles.go @@ -43,7 +43,7 @@ type UpdateCustomRepoRoleRequest struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` BaseRole *string `json:"base_role,omitempty"` - Permissions []string `json:"permissions,omitempty"` + Permissions []string `json:"permissions,omitzero"` } // RepoFineGrainedPermission represents a fine-grained permission that can be used in a custom repository role.