Skip to content

Commit

Permalink
feat!: Add support for multi-select Custom Properties (#3200)
Browse files Browse the repository at this point in the history
Fixes #3198.

BREAKING CHANGE: CustomPropertyValue.Value is changed from *string to interface{} to support string and []string values.
  • Loading branch information
arymoraes authored Jul 24, 2024
1 parent d5e03d5 commit ff0223d
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 25 deletions.
8 changes: 0 additions & 8 deletions github/github-accessors.go

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

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

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

39 changes: 37 additions & 2 deletions github/orgs_properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package github

import (
"context"
"encoding/json"
"fmt"
)

Expand Down Expand Up @@ -40,8 +41,42 @@ type RepoCustomPropertyValue struct {

// CustomPropertyValue represents a custom property value.
type CustomPropertyValue struct {
PropertyName string `json:"property_name"`
Value *string `json:"value,omitempty"`
PropertyName string `json:"property_name"`
Value interface{} `json:"value,omitempty"`
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// This helps us handle the fact that Value can be either a string, []string, or nil.
func (cpv *CustomPropertyValue) UnmarshalJSON(data []byte) error {
type aliasCustomPropertyValue CustomPropertyValue
aux := &struct {
*aliasCustomPropertyValue
}{
aliasCustomPropertyValue: (*aliasCustomPropertyValue)(cpv),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}

switch v := aux.Value.(type) {
case nil:
cpv.Value = nil
case string:
cpv.Value = v
case []interface{}:
strSlice := make([]string, len(v))
for i, item := range v {
if str, ok := item.(string); ok {
strSlice[i] = str
} else {
return fmt.Errorf("non-string value in string array")
}
}
cpv.Value = strSlice
default:
return fmt.Errorf("unexpected value type: %T", v)
}
return nil
}

// GetAllCustomProperties gets all custom properties that are defined for the specified organization.
Expand Down
92 changes: 90 additions & 2 deletions github/orgs_properties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
{
"property_name": "service",
"value": "web"
},
{
"property_name": "languages",
"value": ["Go", "JavaScript"]
},
{
"property_name": "null_property",
"value": null
}
]
}]`)
Expand All @@ -318,11 +326,19 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
Properties: []*CustomPropertyValue{
{
PropertyName: "environment",
Value: String("production"),
Value: "production",
},
{
PropertyName: "service",
Value: String("web"),
Value: "web",
},
{
PropertyName: "languages",
Value: []string{"Go", "JavaScript"},
},
{
PropertyName: "null_property",
Value: nil,
},
},
},
Expand All @@ -348,6 +364,78 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
})
}

func TestCustomPropertyValue_UnmarshalJSON(t *testing.T) {
tests := map[string]struct {
data string
want *CustomPropertyValue
wantErr bool
}{
"Invalid JSON": {
data: `{`,
want: &CustomPropertyValue{},
wantErr: true,
},
"String value": {
data: `{
"property_name": "environment",
"value": "production"
}`,
want: &CustomPropertyValue{
PropertyName: "environment",
Value: "production",
},
wantErr: false,
},
"Array of strings value": {
data: `{
"property_name": "languages",
"value": ["Go", "JavaScript"]
}`,
want: &CustomPropertyValue{
PropertyName: "languages",
Value: []string{"Go", "JavaScript"},
},
wantErr: false,
},
"Non-string value in array": {
data: `{
"property_name": "languages",
"value": ["Go", 42]
}`,
want: &CustomPropertyValue{
PropertyName: "languages",
Value: nil,
},
wantErr: true,
},
"Unexpected value type": {
data: `{
"property_name": "environment",
"value": {"invalid": "type"}
}`,
want: &CustomPropertyValue{
PropertyName: "environment",
Value: nil,
},
wantErr: true,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
cpv := &CustomPropertyValue{}
err := cpv.UnmarshalJSON([]byte(tc.data))
if (err != nil) != tc.wantErr {
t.Errorf("CustomPropertyValue.UnmarshalJSON error = %v, wantErr %v", err, tc.wantErr)
return
}
if !tc.wantErr && !cmp.Equal(tc.want, cpv) {
t.Errorf("CustomPropertyValue.UnmarshalJSON expected %+v, got %+v", tc.want, cpv)
}
})
}
}

func TestOrganizationsService_CreateOrUpdateRepoCustomPropertyValues(t *testing.T) {
client, mux, _, teardown := setup()
defer teardown()
Expand Down
22 changes: 19 additions & 3 deletions github/repos_properties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ func TestRepositoriesService_GetAllCustomPropertyValues(t *testing.T) {
{
"property_name": "service",
"value": "web"
},
{
"property_name": "languages",
"value": ["Go", "JavaScript"]
},
{
"property_name": "null_property",
"value": null
}
]`)
})
Expand All @@ -41,11 +49,19 @@ func TestRepositoriesService_GetAllCustomPropertyValues(t *testing.T) {
want := []*CustomPropertyValue{
{
PropertyName: "environment",
Value: String("production"),
Value: "production",
},
{
PropertyName: "service",
Value: String("web"),
Value: "web",
},
{
PropertyName: "languages",
Value: []string{"Go", "JavaScript"},
},
{
PropertyName: "null_property",
Value: nil,
},
}

Expand Down Expand Up @@ -77,7 +93,7 @@ func TestRepositoriesService_CreateOrUpdateCustomProperties(t *testing.T) {
RepoCustomProperty := []*CustomPropertyValue{
{
PropertyName: "environment",
Value: String("production"),
Value: "production",
},
}
_, err := client.Repositories.CreateOrUpdateCustomProperties(ctx, "usr", "r", RepoCustomProperty)
Expand Down

0 comments on commit ff0223d

Please sign in to comment.