Skip to content

Commit ff0223d

Browse files
authored
feat!: Add support for multi-select Custom Properties (#3200)
Fixes #3198. BREAKING CHANGE: CustomPropertyValue.Value is changed from *string to interface{} to support string and []string values.
1 parent d5e03d5 commit ff0223d

File tree

5 files changed

+146
-25
lines changed

5 files changed

+146
-25
lines changed

github/github-accessors.go

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

github/github-accessors_test.go

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

github/orgs_properties.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package github
77

88
import (
99
"context"
10+
"encoding/json"
1011
"fmt"
1112
)
1213

@@ -40,8 +41,42 @@ type RepoCustomPropertyValue struct {
4041

4142
// CustomPropertyValue represents a custom property value.
4243
type CustomPropertyValue struct {
43-
PropertyName string `json:"property_name"`
44-
Value *string `json:"value,omitempty"`
44+
PropertyName string `json:"property_name"`
45+
Value interface{} `json:"value,omitempty"`
46+
}
47+
48+
// UnmarshalJSON implements the json.Unmarshaler interface.
49+
// This helps us handle the fact that Value can be either a string, []string, or nil.
50+
func (cpv *CustomPropertyValue) UnmarshalJSON(data []byte) error {
51+
type aliasCustomPropertyValue CustomPropertyValue
52+
aux := &struct {
53+
*aliasCustomPropertyValue
54+
}{
55+
aliasCustomPropertyValue: (*aliasCustomPropertyValue)(cpv),
56+
}
57+
if err := json.Unmarshal(data, &aux); err != nil {
58+
return err
59+
}
60+
61+
switch v := aux.Value.(type) {
62+
case nil:
63+
cpv.Value = nil
64+
case string:
65+
cpv.Value = v
66+
case []interface{}:
67+
strSlice := make([]string, len(v))
68+
for i, item := range v {
69+
if str, ok := item.(string); ok {
70+
strSlice[i] = str
71+
} else {
72+
return fmt.Errorf("non-string value in string array")
73+
}
74+
}
75+
cpv.Value = strSlice
76+
default:
77+
return fmt.Errorf("unexpected value type: %T", v)
78+
}
79+
return nil
4580
}
4681

4782
// GetAllCustomProperties gets all custom properties that are defined for the specified organization.

github/orgs_properties_test.go

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,14 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
296296
{
297297
"property_name": "service",
298298
"value": "web"
299+
},
300+
{
301+
"property_name": "languages",
302+
"value": ["Go", "JavaScript"]
303+
},
304+
{
305+
"property_name": "null_property",
306+
"value": null
299307
}
300308
]
301309
}]`)
@@ -318,11 +326,19 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
318326
Properties: []*CustomPropertyValue{
319327
{
320328
PropertyName: "environment",
321-
Value: String("production"),
329+
Value: "production",
322330
},
323331
{
324332
PropertyName: "service",
325-
Value: String("web"),
333+
Value: "web",
334+
},
335+
{
336+
PropertyName: "languages",
337+
Value: []string{"Go", "JavaScript"},
338+
},
339+
{
340+
PropertyName: "null_property",
341+
Value: nil,
326342
},
327343
},
328344
},
@@ -348,6 +364,78 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
348364
})
349365
}
350366

367+
func TestCustomPropertyValue_UnmarshalJSON(t *testing.T) {
368+
tests := map[string]struct {
369+
data string
370+
want *CustomPropertyValue
371+
wantErr bool
372+
}{
373+
"Invalid JSON": {
374+
data: `{`,
375+
want: &CustomPropertyValue{},
376+
wantErr: true,
377+
},
378+
"String value": {
379+
data: `{
380+
"property_name": "environment",
381+
"value": "production"
382+
}`,
383+
want: &CustomPropertyValue{
384+
PropertyName: "environment",
385+
Value: "production",
386+
},
387+
wantErr: false,
388+
},
389+
"Array of strings value": {
390+
data: `{
391+
"property_name": "languages",
392+
"value": ["Go", "JavaScript"]
393+
}`,
394+
want: &CustomPropertyValue{
395+
PropertyName: "languages",
396+
Value: []string{"Go", "JavaScript"},
397+
},
398+
wantErr: false,
399+
},
400+
"Non-string value in array": {
401+
data: `{
402+
"property_name": "languages",
403+
"value": ["Go", 42]
404+
}`,
405+
want: &CustomPropertyValue{
406+
PropertyName: "languages",
407+
Value: nil,
408+
},
409+
wantErr: true,
410+
},
411+
"Unexpected value type": {
412+
data: `{
413+
"property_name": "environment",
414+
"value": {"invalid": "type"}
415+
}`,
416+
want: &CustomPropertyValue{
417+
PropertyName: "environment",
418+
Value: nil,
419+
},
420+
wantErr: true,
421+
},
422+
}
423+
424+
for name, tc := range tests {
425+
t.Run(name, func(t *testing.T) {
426+
cpv := &CustomPropertyValue{}
427+
err := cpv.UnmarshalJSON([]byte(tc.data))
428+
if (err != nil) != tc.wantErr {
429+
t.Errorf("CustomPropertyValue.UnmarshalJSON error = %v, wantErr %v", err, tc.wantErr)
430+
return
431+
}
432+
if !tc.wantErr && !cmp.Equal(tc.want, cpv) {
433+
t.Errorf("CustomPropertyValue.UnmarshalJSON expected %+v, got %+v", tc.want, cpv)
434+
}
435+
})
436+
}
437+
}
438+
351439
func TestOrganizationsService_CreateOrUpdateRepoCustomPropertyValues(t *testing.T) {
352440
client, mux, _, teardown := setup()
353441
defer teardown()

github/repos_properties_test.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ func TestRepositoriesService_GetAllCustomPropertyValues(t *testing.T) {
2828
{
2929
"property_name": "service",
3030
"value": "web"
31+
},
32+
{
33+
"property_name": "languages",
34+
"value": ["Go", "JavaScript"]
35+
},
36+
{
37+
"property_name": "null_property",
38+
"value": null
3139
}
3240
]`)
3341
})
@@ -41,11 +49,19 @@ func TestRepositoriesService_GetAllCustomPropertyValues(t *testing.T) {
4149
want := []*CustomPropertyValue{
4250
{
4351
PropertyName: "environment",
44-
Value: String("production"),
52+
Value: "production",
4553
},
4654
{
4755
PropertyName: "service",
48-
Value: String("web"),
56+
Value: "web",
57+
},
58+
{
59+
PropertyName: "languages",
60+
Value: []string{"Go", "JavaScript"},
61+
},
62+
{
63+
PropertyName: "null_property",
64+
Value: nil,
4965
},
5066
}
5167

@@ -77,7 +93,7 @@ func TestRepositoriesService_CreateOrUpdateCustomProperties(t *testing.T) {
7793
RepoCustomProperty := []*CustomPropertyValue{
7894
{
7995
PropertyName: "environment",
80-
Value: String("production"),
96+
Value: "production",
8197
},
8298
}
8399
_, err := client.Repositories.CreateOrUpdateCustomProperties(ctx, "usr", "r", RepoCustomProperty)

0 commit comments

Comments
 (0)