From 9f33643dccb4b40421f3d378eafd1a095782720f Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Wed, 16 Sep 2026 11:42:36 +0200 Subject: [PATCH 1/2] feat: Support explicit custom property values Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 3 ++ CONTRIBUTING.md | 3 +- github/github-accessors.go | 8 +++ github/github-accessors_test.go | 11 ++++ github/orgs_properties.go | 2 + github/orgs_properties_test.go | 96 +++++++++++++++++++++++---------- 6 files changed, 93 insertions(+), 30 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 42f9c51e198..fcde7aaf84a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,3 +7,6 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for everything you need, in particular: - [Submitting a patch](./CONTRIBUTING.md#submitting-a-patch) - workflow and the scripts to run before submitting (`script/fmt.sh`, `script/test.sh`, `script/lint.sh`, `script/generate.sh`). - [Tips](./CONTRIBUTING.md#tips) - expectations for (AI-driven) PRs. - [Code Guidelines](./CONTRIBUTING.md#code-guidelines) - file organization, naming conventions, type conventions, JSON tags, pagination, generated code, and testing patterns. + +For optional pointer primitive JSON fields, use `omitempty`; the repository's +`structfield` linter rejects `omitzero` on pointer primitives. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3cdb417b766..d6cd8314604 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -414,7 +414,8 @@ type RepositoryRuleset struct { ``` For optional boolean fields where you need to distinguish between `false` -and "not set", use `*bool` with `omitzero`. +and "not set", use `*bool` with `omitempty`. A non-nil pointer preserves an +explicit `false`, while a nil pointer is omitted. #### Response Bodies diff --git a/github/github-accessors.go b/github/github-accessors.go index f1759a7e1f2..64d7524bd1a 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -13934,6 +13934,14 @@ func (c *CustomProperty) GetRequired() bool { return *c.Required } +// GetRequireExplicitValues returns the RequireExplicitValues field if it's non-nil, zero value otherwise. +func (c *CustomProperty) GetRequireExplicitValues() bool { + if c == nil || c.RequireExplicitValues == nil { + return false + } + return *c.RequireExplicitValues +} + // GetSourceType returns the SourceType field if it's non-nil, zero value otherwise. func (c *CustomProperty) GetSourceType() string { if c == nil || c.SourceType == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 9a5069fcdf0..d88ed1b5df2 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -17601,6 +17601,17 @@ func TestCustomProperty_GetRequired(tt *testing.T) { c.GetRequired() } +func TestCustomProperty_GetRequireExplicitValues(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CustomProperty{RequireExplicitValues: &zeroValue} + c.GetRequireExplicitValues() + c = &CustomProperty{} + c.GetRequireExplicitValues() + c = nil + c.GetRequireExplicitValues() +} + func TestCustomProperty_GetSourceType(tt *testing.T) { tt.Parallel() var zeroValue string diff --git a/github/orgs_properties.go b/github/orgs_properties.go index f3492c47e87..2955082ab14 100644 --- a/github/orgs_properties.go +++ b/github/orgs_properties.go @@ -38,6 +38,8 @@ type CustomProperty struct { ValueType PropertyValueType `json:"value_type"` // Whether the property is required. Required *bool `json:"required,omitempty"` + // Whether setting property values is mandatory. + RequireExplicitValues *bool `json:"require_explicit_values,omitempty"` // Default value of the property. DefaultValue any `json:"default_value,omitempty"` // Short description of the property. diff --git a/github/orgs_properties_test.go b/github/orgs_properties_test.go index 10de42fe2da..1bfee97612e 100644 --- a/github/orgs_properties_test.go +++ b/github/orgs_properties_test.go @@ -25,6 +25,7 @@ func TestOrganizationsService_GetAllCustomProperties(t *testing.T) { "property_name": "name", "value_type": "single_select", "required": true, + "require_explicit_values": true, "default_value": "production", "description": "Prod or dev environment", "allowed_values":[ @@ -76,13 +77,14 @@ func TestOrganizationsService_GetAllCustomProperties(t *testing.T) { want := []*CustomProperty{ { - PropertyName: new("name"), - ValueType: PropertyValueTypeSingleSelect, - Required: new(true), - DefaultValue: "production", - Description: new("Prod or dev environment"), - AllowedValues: []string{"production", "development"}, - ValuesEditableBy: new("org_actors"), + PropertyName: new("name"), + ValueType: PropertyValueTypeSingleSelect, + Required: new(true), + RequireExplicitValues: new(true), + DefaultValue: "production", + Description: new("Prod or dev environment"), + AllowedValues: []string{"production", "development"}, + ValuesEditableBy: new("org_actors"), }, { PropertyName: new("test"), @@ -135,9 +137,10 @@ func TestOrganizationsService_CreateOrUpdateCustomProperties(t *testing.T) { }{ Properties: []*CustomProperty{ { - PropertyName: new("name"), - ValueType: PropertyValueTypeSingleSelect, - Required: new(true), + PropertyName: new("name"), + ValueType: PropertyValueTypeSingleSelect, + Required: new(true), + RequireExplicitValues: new(false), }, { PropertyName: new("service"), @@ -153,7 +156,8 @@ func TestOrganizationsService_CreateOrUpdateCustomProperties(t *testing.T) { { "property_name": "name", "value_type": "single_select", - "required": true + "required": true, + "require_explicit_values": false }, { "property_name": "service", @@ -170,9 +174,10 @@ func TestOrganizationsService_CreateOrUpdateCustomProperties(t *testing.T) { want := []*CustomProperty{ { - PropertyName: new("name"), - ValueType: PropertyValueTypeSingleSelect, - Required: new(true), + PropertyName: new("name"), + ValueType: PropertyValueTypeSingleSelect, + Required: new(true), + RequireExplicitValues: new(false), }, { PropertyName: new("service"), @@ -249,12 +254,24 @@ func TestOrganizationsService_CreateOrUpdateCustomProperty(t *testing.T) { t.Parallel() client, mux, _ := setup(t) + input := &CustomProperty{ + ValueType: PropertyValueTypeSingleSelect, + Required: new(true), + RequireExplicitValues: new(false), + DefaultValue: "production", + Description: new("Prod or dev environment"), + AllowedValues: []string{"production", "development"}, + ValuesEditableBy: new("org_actors"), + } + mux.HandleFunc("/orgs/o/properties/schema/name", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PUT") + testJSONBody(t, r, input) fmt.Fprint(w, `{ "property_name": "name", "value_type": "single_select", "required": true, + "require_explicit_values": false, "default_value": "production", "description": "Prod or dev environment", "allowed_values":[ @@ -266,26 +283,20 @@ func TestOrganizationsService_CreateOrUpdateCustomProperty(t *testing.T) { }) ctx := t.Context() - property, _, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, "o", "name", &CustomProperty{ - ValueType: PropertyValueTypeSingleSelect, - Required: new(true), - DefaultValue: "production", - Description: new("Prod or dev environment"), - AllowedValues: []string{"production", "development"}, - ValuesEditableBy: new("org_actors"), - }) + property, _, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, "o", "name", input) if err != nil { t.Errorf("Organizations.CreateOrUpdateCustomProperty returned error: %v", err) } want := &CustomProperty{ - PropertyName: new("name"), - ValueType: PropertyValueTypeSingleSelect, - Required: new(true), - DefaultValue: "production", - Description: new("Prod or dev environment"), - AllowedValues: []string{"production", "development"}, - ValuesEditableBy: new("org_actors"), + PropertyName: new("name"), + ValueType: PropertyValueTypeSingleSelect, + Required: new(true), + RequireExplicitValues: new(false), + DefaultValue: "production", + Description: new("Prod or dev environment"), + AllowedValues: []string{"production", "development"}, + ValuesEditableBy: new("org_actors"), } if !cmp.Equal(property, want) { t.Errorf("Organizations.CreateOrUpdateCustomProperty returned %+v, want %+v", property, want) @@ -302,6 +313,33 @@ func TestOrganizationsService_CreateOrUpdateCustomProperty(t *testing.T) { }) } +func TestCustomProperty_RequireExplicitValuesJSON(t *testing.T) { + t.Parallel() + tests := map[string]struct { + property *CustomProperty + want string + }{ + "omitted": { + property: &CustomProperty{ValueType: PropertyValueTypeString}, + want: `{"value_type":"string"}`, + }, + "explicit false": { + property: &CustomProperty{ + ValueType: PropertyValueTypeString, + RequireExplicitValues: new(false), + }, + want: `{"value_type":"string","require_explicit_values":false}`, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + testJSONMarshal(t, test.property, test.want) + }) + } +} + func TestOrganizationsService_RemoveCustomProperty(t *testing.T) { t.Parallel() client, mux, _ := setup(t) From dac265e8c161e10b58740ed4a426eccf1a8cecee Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Wed, 16 Sep 2026 13:59:30 +0200 Subject: [PATCH 2/2] fix: Use omitzero for explicit values Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .golangci.yml | 1 + AGENTS.md | 3 --- CONTRIBUTING.md | 3 +-- github/orgs_properties.go | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c757facdda9..90a4b0bba2a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -365,6 +365,7 @@ linters: - CreateTag.Object # TODO: Git - CreateTag.Tag # TODO: Git - CreateTag.Type # TODO: Git + - CustomProperty.RequireExplicitValues - DismissalRestrictionsRequest.Apps # TODO: Repositories - DismissalRestrictionsRequest.Teams # TODO: Repositories - DismissalRestrictionsRequest.Users # TODO: Repositories diff --git a/AGENTS.md b/AGENTS.md index fcde7aaf84a..42f9c51e198 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,3 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for everything you need, in particular: - [Submitting a patch](./CONTRIBUTING.md#submitting-a-patch) - workflow and the scripts to run before submitting (`script/fmt.sh`, `script/test.sh`, `script/lint.sh`, `script/generate.sh`). - [Tips](./CONTRIBUTING.md#tips) - expectations for (AI-driven) PRs. - [Code Guidelines](./CONTRIBUTING.md#code-guidelines) - file organization, naming conventions, type conventions, JSON tags, pagination, generated code, and testing patterns. - -For optional pointer primitive JSON fields, use `omitempty`; the repository's -`structfield` linter rejects `omitzero` on pointer primitives. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6cd8314604..3cdb417b766 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -414,8 +414,7 @@ type RepositoryRuleset struct { ``` For optional boolean fields where you need to distinguish between `false` -and "not set", use `*bool` with `omitempty`. A non-nil pointer preserves an -explicit `false`, while a nil pointer is omitted. +and "not set", use `*bool` with `omitzero`. #### Response Bodies diff --git a/github/orgs_properties.go b/github/orgs_properties.go index 2955082ab14..50cf5a7b6bc 100644 --- a/github/orgs_properties.go +++ b/github/orgs_properties.go @@ -39,7 +39,7 @@ type CustomProperty struct { // Whether the property is required. Required *bool `json:"required,omitempty"` // Whether setting property values is mandatory. - RequireExplicitValues *bool `json:"require_explicit_values,omitempty"` + RequireExplicitValues *bool `json:"require_explicit_values,omitzero"` // Default value of the property. DefaultValue any `json:"default_value,omitempty"` // Short description of the property.