Skip to content

Commit 485cfab

Browse files
feat: Add native Go 1.23 Iterator support
1 parent e32b6b2 commit 485cfab

18 files changed

+2916
-420
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ vendor/
1919
# golangci-lint -v custom generates the following local file:
2020
custom-gcl
2121
custom-gcl.exe
22+
gen-iterators

example/go.mod

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ require (
99
github.com/gofri/go-github-ratelimit/v2 v2.0.2
1010
github.com/google/go-github/v81 v81.0.0
1111
github.com/sigstore/sigstore-go v0.6.1
12-
golang.org/x/crypto v0.47.0
13-
golang.org/x/term v0.39.0
12+
golang.org/x/crypto v0.46.0
13+
golang.org/x/term v0.38.0
1414
google.golang.org/appengine v1.6.8
1515
)
1616

@@ -87,11 +87,11 @@ require (
8787
go.uber.org/multierr v1.11.0 // indirect
8888
go.uber.org/zap v1.27.0 // indirect
8989
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
90-
golang.org/x/mod v0.31.0 // indirect
91-
golang.org/x/net v0.48.0 // indirect
90+
golang.org/x/mod v0.30.0 // indirect
91+
golang.org/x/net v0.47.0 // indirect
9292
golang.org/x/sync v0.19.0 // indirect
93-
golang.org/x/sys v0.40.0 // indirect
94-
golang.org/x/text v0.33.0 // indirect
93+
golang.org/x/sys v0.39.0 // indirect
94+
golang.org/x/text v0.32.0 // indirect
9595
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
9696
google.golang.org/protobuf v1.34.2 // indirect
9797
gopkg.in/ini.v1 v1.67.0 // indirect

example/go.sum

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -355,18 +355,18 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
355355
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
356356
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
357357
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
358-
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
359-
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
358+
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
359+
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
360360
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
361361
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
362362
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
363-
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
364-
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
363+
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
364+
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
365365
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
366366
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
367367
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
368-
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
369-
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
368+
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
369+
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
370370
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
371371
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
372372
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -378,18 +378,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
378378
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
379379
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
380380
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
381-
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
382-
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
381+
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
382+
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
383383
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
384384
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
385-
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
386-
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
385+
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
386+
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
387387
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
388388
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
389389
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
390390
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
391-
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
392-
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
391+
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
392+
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
393393
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
394394
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
395395
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

gen-iterators

-4.78 MB
Binary file not shown.

github/enterprise_properties_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func TestEnterpriseService_GetAllCustomProperties(t *testing.T) {
5555
PropertyName: Ptr("name"),
5656
ValueType: PropertyValueTypeSingleSelect,
5757
Required: Ptr(true),
58-
DefaultValue: "production",
58+
DefaultValue: Ptr("production"),
5959
Description: Ptr("Prod or dev environment"),
6060
AllowedValues: []string{"production", "development"},
6161
ValuesEditableBy: Ptr("org_actors"),
@@ -178,7 +178,7 @@ func TestEnterpriseService_GetCustomProperty(t *testing.T) {
178178
PropertyName: Ptr("name"),
179179
ValueType: PropertyValueTypeSingleSelect,
180180
Required: Ptr(true),
181-
DefaultValue: "production",
181+
DefaultValue: Ptr("production"),
182182
Description: Ptr("Prod or dev environment"),
183183
AllowedValues: []string{"production", "development"},
184184
ValuesEditableBy: Ptr("org_actors"),
@@ -222,7 +222,7 @@ func TestEnterpriseService_CreateOrUpdateCustomProperty(t *testing.T) {
222222
property, _, err := client.Enterprise.CreateOrUpdateCustomProperty(ctx, "e", "name", &CustomProperty{
223223
ValueType: PropertyValueTypeSingleSelect,
224224
Required: Ptr(true),
225-
DefaultValue: "production",
225+
DefaultValue: Ptr("production"),
226226
Description: Ptr("Prod or dev environment"),
227227
AllowedValues: []string{"production", "development"},
228228
ValuesEditableBy: Ptr("org_actors"),
@@ -235,7 +235,7 @@ func TestEnterpriseService_CreateOrUpdateCustomProperty(t *testing.T) {
235235
PropertyName: Ptr("name"),
236236
ValueType: PropertyValueTypeSingleSelect,
237237
Required: Ptr(true),
238-
DefaultValue: "production",
238+
DefaultValue: Ptr("production"),
239239
Description: Ptr("Prod or dev environment"),
240240
AllowedValues: []string{"production", "development"},
241241
ValuesEditableBy: Ptr("org_actors"),

github/event_types_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13732,7 +13732,7 @@ func TestCustomPropertyEvent_Marshal(t *testing.T) {
1373213732
ValueType: PropertyValueTypeSingleSelect,
1373313733
SourceType: Ptr("enterprise"),
1373413734
Required: Ptr(true),
13735-
DefaultValue: "production",
13735+
DefaultValue: Ptr("production"),
1373613736
Description: Ptr("Prod or dev environment"),
1373713737
AllowedValues: []string{"production", "development"},
1373813738
ValuesEditableBy: Ptr("org_actors"),
Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@ import (
1818
"go/token"
1919
"log"
2020
"os"
21-
"path/filepath"
2221
"slices"
2322
"strings"
2423
"text/template"
2524
)
2625

2726
const (
28-
fileSuffix = "_iterators.go"
27+
fileSuffix = "iterators.go"
2928
)
3029

3130
var (
@@ -34,6 +33,8 @@ var (
3433
sourceTmpl = template.Must(template.New("source").Funcs(template.FuncMap{
3534
"hasPrefix": strings.HasPrefix,
3635
}).Parse(source))
36+
37+
testTmpl = template.Must(template.New("test").Parse(test))
3738
)
3839

3940
func logf(fmt string, args ...any) {
@@ -46,17 +47,14 @@ func main() {
4647
flag.Parse()
4748
fset := token.NewFileSet()
4849

49-
pkgs, err := parser.ParseDir(fset, "github", sourceFilter, parser.ParseComments)
50+
// Parse the current directory
51+
pkgs, err := parser.ParseDir(fset, ".", sourceFilter, 0)
5052
if err != nil {
5153
log.Fatal(err)
5254
return
5355
}
5456

5557
for pkgName, pkg := range pkgs {
56-
if pkgName != "github" {
57-
continue
58-
}
59-
6058
t := &templateData{
6159
Package: pkgName,
6260
Methods: []*method{},
@@ -81,7 +79,7 @@ func main() {
8179
}
8280

8381
func sourceFilter(fi os.FileInfo) bool {
84-
return !strings.HasSuffix(fi.Name(), "_test.go") && !strings.HasSuffix(fi.Name(), fileSuffix)
82+
return !strings.HasSuffix(fi.Name(), "_test.go") && !strings.HasSuffix(fi.Name(), fileSuffix) && !strings.HasPrefix(fi.Name(), "gen-")
8583
}
8684

8785
type templateData struct {
@@ -99,17 +97,26 @@ type structDef struct {
9997
type method struct {
10098
RecvType string
10199
RecvVar string
100+
ClientField string
102101
MethodName string
103102
IterMethod string
104103
Args string
105104
CallArgs string
105+
ZeroArgs string
106106
ReturnType string
107107
OptsType string
108108
OptsName string
109109
OptsIsPtr bool
110110
UseListOptions bool
111111
UsePage bool
112-
MetaOps []string
112+
TestJSON string
113+
}
114+
115+
// customTestJSON maps method names to the JSON response they expect in tests.
116+
// This is needed for methods that internally unmarshal a wrapper struct
117+
// even though they return a slice.
118+
var customTestJSON = map[string]string{
119+
"ListUserInstallations": `{"installations": []}`,
113120
}
114121

115122
func (t *templateData) processStructs(f *ast.File) {
@@ -180,6 +187,21 @@ func (t *templateData) hasIntPage(structName string) bool {
180187
return false
181188
}
182189

190+
func getZeroValue(typeStr string) string {
191+
switch typeStr {
192+
case "int", "int64", "int32":
193+
return "0"
194+
case "string":
195+
return `""`
196+
case "bool":
197+
return "false"
198+
case "context.Context":
199+
return "context.Background()"
200+
default:
201+
return "nil"
202+
}
203+
}
204+
183205
func (t *templateData) processMethods(f *ast.File) error {
184206
for _, decl := range f.Decls {
185207
fd, ok := decl.(*ast.FuncDecl)
@@ -219,6 +241,7 @@ func (t *templateData) processMethods(f *ast.File) error {
219241

220242
args := []string{}
221243
callArgs := []string{}
244+
zeroArgs := []string{}
222245
var optsType string
223246
var optsName string
224247
hasOpts := false
@@ -229,6 +252,7 @@ func (t *templateData) processMethods(f *ast.File) error {
229252
for _, name := range field.Names {
230253
args = append(args, fmt.Sprintf("%s %s", name.Name, typeStr))
231254
callArgs = append(callArgs, name.Name)
255+
zeroArgs = append(zeroArgs, getZeroValue(typeStr))
232256

233257
if strings.HasSuffix(typeStr, "Options") {
234258
optsType = strings.TrimPrefix(typeStr, "*")
@@ -251,19 +275,36 @@ func (t *templateData) processMethods(f *ast.File) error {
251275
continue
252276
}
253277

278+
recType := strings.TrimPrefix(recvType, "*")
279+
clientField := strings.TrimSuffix(recType, "Service")
280+
if clientField == "Migration" {
281+
clientField = "Migrations"
282+
}
283+
if clientField == "s" {
284+
logf("WARNING: clientField is 's' for %s.%s (recvType=%s)", recvType, fd.Name.Name, recType)
285+
}
286+
287+
testJSON := "[]"
288+
if val, ok := customTestJSON[fd.Name.Name]; ok {
289+
testJSON = val
290+
}
291+
254292
m := &method{
255-
RecvType: strings.TrimPrefix(recvType, "*"),
293+
RecvType: recType,
256294
RecvVar: recvVar,
295+
ClientField: clientField,
257296
MethodName: fd.Name.Name,
258297
IterMethod: fd.Name.Name + "Iter",
259298
Args: strings.Join(args, ", "),
260299
CallArgs: strings.Join(callArgs, ", "),
300+
ZeroArgs: strings.Join(zeroArgs, ", "),
261301
ReturnType: eltType,
262302
OptsType: optsType,
263303
OptsName: optsName,
264304
OptsIsPtr: optsIsPtr,
265305
UseListOptions: useListOptions,
266306
UsePage: usePage,
307+
TestJSON: testJSON,
267308
}
268309
t.Methods = append(t.Methods, m)
269310
}
@@ -299,19 +340,23 @@ func (t *templateData) dump() error {
299340
return strings.Compare(a.MethodName, b.MethodName)
300341
})
301342

302-
var buf bytes.Buffer
303-
if err := sourceTmpl.Execute(&buf, t); err != nil {
304-
return err
343+
processTemplate := func(tmpl *template.Template, filename string) error {
344+
var buf bytes.Buffer
345+
if err := tmpl.Execute(&buf, t); err != nil {
346+
return err
347+
}
348+
clean, err := format.Source(buf.Bytes())
349+
if err != nil {
350+
return fmt.Errorf("format.Source: %v\n%s", err, buf.String())
351+
}
352+
logf("Writing %v...", filename)
353+
return os.WriteFile(filename, clean, 0644)
305354
}
306355

307-
clean, err := format.Source(buf.Bytes())
308-
if err != nil {
309-
return fmt.Errorf("format.Source: %v\n%s", err, buf.String())
356+
if err := processTemplate(sourceTmpl, "iterators.go"); err != nil {
357+
return err
310358
}
311-
312-
filename := filepath.Join("github", "iterators.go")
313-
logf("Writing %v...", filename)
314-
return os.WriteFile(filename, clean, 0644)
359+
return processTemplate(testTmpl, "iterators_gen_test.go")
315360
}
316361

317362
const source = `// Copyright 2025 The go-github AUTHORS. All rights reserved.
@@ -370,3 +415,41 @@ func ({{.RecvVar}} *{{.RecvType}}) {{.IterMethod}}({{.Args}}) iter.Seq2[{{.Retur
370415
}
371416
{{end}}
372417
`
418+
419+
const test = `// Copyright 2025 The go-github AUTHORS. All rights reserved.
420+
//
421+
// Use of this source code is governed by a BSD-style
422+
// license that can be found in the LICENSE file.
423+
424+
// Code generated by gen-iterators; DO NOT EDIT.
425+
426+
package {{.Package}}
427+
428+
import (
429+
"context"
430+
"fmt"
431+
"net/http"
432+
"testing"
433+
)
434+
435+
{{range .Methods}}
436+
func Test{{.RecvType}}_{{.IterMethod}}(t *testing.T) {
437+
t.Parallel()
438+
client, mux, _ := setup(t)
439+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
440+
fmt.Fprint(w, ` + "`" + `{{.TestJSON}}` + "`" + `)
441+
})
442+
443+
ctx := context.Background()
444+
_ = ctx // avoid unused
445+
446+
// Call iterator with zero values
447+
iter := client.{{.ClientField}}.{{.IterMethod}}({{.ZeroArgs}})
448+
for _, err := range iter {
449+
if err != nil {
450+
t.Errorf("Unexpected error: %v", err)
451+
}
452+
}
453+
}
454+
{{end}}
455+
`

github/github-accessors.go

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

0 commit comments

Comments
 (0)