Skip to content

Commit 934a446

Browse files
committed
handle missing types (#12)
1 parent bb327e1 commit 934a446

File tree

2 files changed

+224
-19
lines changed

2 files changed

+224
-19
lines changed

iter.go

Lines changed: 120 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"reflect"
1010
"strconv"
1111
"strings"
12+
"time"
1213

1314
"github.com/google/go-github/v65/github"
1415
)
@@ -123,7 +124,10 @@ func (it *Iterator[T, O]) All() iter.Seq[T] {
123124
vals[k] = v[0]
124125
}
125126

126-
updateOptions(it.opt, vals)
127+
if err := updateOptions(it.opt, vals); err != nil {
128+
it.err = err
129+
return
130+
}
127131
}
128132
}
129133
}
@@ -200,10 +204,17 @@ func (it *Iterator[T, O]) do() ([]T, *github.Response, error) {
200204
return nil, nil, errors.New("no func provided")
201205
}
202206

207+
var (
208+
stringTypePtr *string
209+
intTypePtr *int
210+
int64TypePtr *int64
211+
boolTypePtr *bool
212+
)
213+
203214
// updateOptions will update the github options based on the provided map and the `url` tag.
204215
// If the field in the struct has a `url` tag it tries to set the value of the field from the one
205216
// found in the map, if any.
206-
func updateOptions(v any, m map[string]string) {
217+
func updateOptions(v any, m map[string]string) error {
207218
valueOf := reflect.ValueOf(v)
208219
typeOf := reflect.TypeOf(v)
209220

@@ -219,29 +230,120 @@ func updateOptions(v any, m map[string]string) {
219230
// if field is of type struct then iterate over the pointer
220231
if structField.Type.Kind() == reflect.Struct {
221232
if fieldValue.CanAddr() {
222-
updateOptions(fieldValue.Addr().Interface(), m)
233+
if err := updateOptions(fieldValue.Addr().Interface(), m); err != nil {
234+
return err
235+
}
223236
}
224237
}
225238

226239
// otherwise check if it has a 'url' tag
227240
urlTag := structField.Tag.Get("url")
228-
if urlTag != "" {
229-
urlParam := strings.Split(urlTag, ",")[0]
230-
231-
if fieldValue.IsValid() && fieldValue.CanSet() {
232-
if v, found := m[urlParam]; found {
233-
switch fieldValue.Kind() {
234-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
235-
if i, err := strconv.Atoi(v); err == nil {
236-
fieldValue.SetInt(int64(i))
237-
}
238-
case reflect.Ptr:
239-
fieldValue.Set(reflect.ValueOf(&v))
240-
default:
241-
fieldValue.Set(reflect.ValueOf(v))
242-
}
241+
if urlTag == "" {
242+
continue
243+
}
244+
245+
if !fieldValue.IsValid() || !fieldValue.CanSet() {
246+
continue
247+
}
248+
249+
urlParam := strings.Split(urlTag, ",")[0]
250+
v, found := m[urlParam]
251+
if !found {
252+
continue
253+
}
254+
255+
switch fieldValue.Kind() {
256+
257+
// handle string
258+
case reflect.String:
259+
fieldValue.Set(reflect.ValueOf(v))
260+
261+
// handle numeric types (int, int8, int16, int32, int64)
262+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
263+
if i, err := strconv.Atoi(v); err == nil {
264+
fieldValue.SetInt(int64(i))
265+
}
266+
267+
// handle bool
268+
case reflect.Bool:
269+
parsedBool, err := strconv.ParseBool(v)
270+
if err != nil {
271+
return fmt.Errorf("error while parsing string '%s' as bool: %s", v, err)
272+
}
273+
fieldValue.Set(reflect.ValueOf(parsedBool))
274+
275+
// handle pointers (*string, *int, *int64, *bool, *time.Time)
276+
case reflect.Pointer:
277+
switch fieldValue.Type() {
278+
279+
// handle *string
280+
case reflect.TypeOf(stringTypePtr):
281+
fieldValue.Set(reflect.ValueOf(&v))
282+
283+
// handle *int
284+
case reflect.TypeOf(intTypePtr):
285+
parsedInt, err := strconv.Atoi(v)
286+
if err != nil {
287+
return fmt.Errorf("error while parsing string '%s' as int: %s", v, err)
243288
}
289+
fieldValue.Set(reflect.ValueOf(&parsedInt))
290+
291+
// handle *int64
292+
case reflect.TypeOf(int64TypePtr):
293+
parsedInt64, err := strconv.ParseInt(v, 10, 64)
294+
if err != nil {
295+
return fmt.Errorf("error while parsing string '%s' as int64: %s", v, err)
296+
}
297+
fieldValue.Set(reflect.ValueOf(&parsedInt64))
298+
299+
// handle *bool
300+
case reflect.TypeOf(boolTypePtr):
301+
parsedBool, err := strconv.ParseBool(v)
302+
if err != nil {
303+
return fmt.Errorf("error while parsing string '%s' as bool: %s", v, err)
304+
}
305+
fieldValue.Set(reflect.ValueOf(&parsedBool))
306+
307+
// handle *time.Time
308+
case reflect.TypeOf(&time.Time{}):
309+
layout := time.RFC3339
310+
if len(v) == len(time.DateOnly) {
311+
layout = time.DateOnly
312+
}
313+
314+
result, err := time.Parse(layout, v)
315+
if err != nil {
316+
return fmt.Errorf("error while parsing string '%s' as time.Time: %s", v, err)
317+
}
318+
319+
fieldValue.Set(reflect.ValueOf(&result))
320+
321+
default:
322+
return fmt.Errorf("cannot set '%s' value to unknown pointer of '%s'", v, fieldValue.Type())
244323
}
324+
325+
case reflect.Struct:
326+
// handle time.Time
327+
if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
328+
layout := time.RFC3339
329+
if len(v) == len(time.DateOnly) {
330+
layout = time.DateOnly
331+
}
332+
333+
result, err := time.Parse(layout, v)
334+
if err != nil {
335+
return fmt.Errorf("error while parsing string '%s' as time.Time: %s", v, err)
336+
}
337+
338+
fieldValue.Set(reflect.ValueOf(result))
339+
} else {
340+
return fmt.Errorf("cannot set '%s' value to unknown struct '%s'", v, fieldValue.Type())
341+
}
342+
343+
default:
344+
return fmt.Errorf("cannot set '%s' value to unknown type '%s'", v, fieldValue.Type())
245345
}
246346
}
347+
348+
return nil
247349
}

iter_test.go

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ghiter
33
import (
44
"reflect"
55
"testing"
6+
"time"
67

78
"github.com/google/go-github/v65/github"
89
)
@@ -13,6 +14,7 @@ func Test_updateOptions(t *testing.T) {
1314
opts any
1415
queryParams map[string]string
1516
expectedOpts any
17+
expectedErr bool
1618
}{
1719
{
1820
name: "Simple Opts with ListOptions",
@@ -78,10 +80,111 @@ func Test_updateOptions(t *testing.T) {
7880
},
7981
},
8082
},
83+
{
84+
name: "date RFC parse",
85+
opts: &github.IssueListCommentsOptions{},
86+
queryParams: map[string]string{
87+
"since": "1989-10-02T00:00:00Z",
88+
},
89+
expectedOpts: &github.IssueListCommentsOptions{
90+
Since: func() *time.Time {
91+
since := time.Date(1989, time.October, 2, 0, 0, 0, 0, time.UTC)
92+
return &since
93+
}(),
94+
},
95+
},
96+
{
97+
name: "date DateTime parse",
98+
opts: &github.IssueListCommentsOptions{},
99+
queryParams: map[string]string{
100+
"since": "1989-10-02",
101+
},
102+
expectedOpts: &github.IssueListCommentsOptions{
103+
Since: func() *time.Time {
104+
since := time.Date(1989, time.October, 2, 0, 0, 0, 0, time.UTC)
105+
return &since
106+
}(),
107+
},
108+
},
109+
{
110+
name: "wrong Date",
111+
opts: &github.IssueListCommentsOptions{},
112+
queryParams: map[string]string{
113+
"since": "1923230Z",
114+
},
115+
expectedErr: true,
116+
},
117+
{
118+
name: "Opts with pointers and multiple ListOptions",
119+
opts: &github.CommitsListOptions{},
120+
queryParams: map[string]string{
121+
"since": "1989-10-02T00:00:00Z",
122+
},
123+
expectedOpts: &github.CommitsListOptions{
124+
Since: time.Date(1989, time.October, 2, 0, 0, 0, 0, time.UTC),
125+
},
126+
},
127+
{
128+
name: "Opts with bool",
129+
opts: &github.ListWorkflowRunsOptions{},
130+
queryParams: map[string]string{
131+
"exclude_pull_requests": "true",
132+
},
133+
expectedOpts: &github.ListWorkflowRunsOptions{
134+
ExcludePullRequests: true,
135+
},
136+
},
137+
{
138+
name: "Opts with bool pointers",
139+
opts: &github.WorkflowRunAttemptOptions{},
140+
queryParams: map[string]string{
141+
"exclude_pull_requests": "true",
142+
},
143+
expectedOpts: &github.WorkflowRunAttemptOptions{
144+
ExcludePullRequests: func() *bool {
145+
exclude := true
146+
return &exclude
147+
}(),
148+
},
149+
},
150+
{
151+
name: "Opts with int pointers",
152+
opts: &github.ListSCIMProvisionedIdentitiesOptions{},
153+
queryParams: map[string]string{
154+
"count": "12",
155+
},
156+
expectedOpts: &github.ListSCIMProvisionedIdentitiesOptions{
157+
Count: func() *int {
158+
count := 12
159+
return &count
160+
}(),
161+
},
162+
},
163+
{
164+
name: "Opts with int64 pointers",
165+
opts: &github.ListCheckRunsOptions{},
166+
queryParams: map[string]string{
167+
"app_id": "12",
168+
},
169+
expectedOpts: &github.ListCheckRunsOptions{
170+
AppID: func() *int64 {
171+
count := int64(12)
172+
return &count
173+
}(),
174+
},
175+
},
81176
}
82177

83178
for _, tc := range tt {
84-
updateOptions(tc.opts, tc.queryParams)
179+
err := updateOptions(tc.opts, tc.queryParams)
180+
181+
if tc.expectedErr {
182+
if err == nil {
183+
t.Fatal("missing expected err\n\n")
184+
}
185+
continue
186+
}
187+
85188
if !reflect.DeepEqual(tc.expectedOpts, tc.opts) {
86189
t.Fatalf("structs are not equal:\nexpected:\t%+v\ngot:\t\t%+v\n", tc.expectedOpts, tc.opts)
87190
}

0 commit comments

Comments
 (0)