diff --git a/ipn/ipnlocal/drive.go b/ipn/ipnlocal/drive.go index 5dcd1af37..98d563d87 100644 --- a/ipn/ipnlocal/drive.go +++ b/ipn/ipnlocal/drive.go @@ -93,8 +93,7 @@ func (b *LocalBackend) driveSetShareLocked(share *drive.Share) (views.SliceView[ addedShare := false var shares []*drive.Share - for i := range existingShares.Len() { - existing := existingShares.At(i) + for _, existing := range existingShares.All() { if existing.Name() != share.Name { if !addedShare && existing.Name() > share.Name { // Add share in order @@ -152,8 +151,7 @@ func (b *LocalBackend) driveRenameShareLocked(oldName, newName string) (views.Sl found := false var shares []*drive.Share - for i := range existingShares.Len() { - existing := existingShares.At(i) + for _, existing := range existingShares.All() { if existing.Name() == newName { return existingShares, os.ErrExist } @@ -213,8 +211,7 @@ func (b *LocalBackend) driveRemoveShareLocked(name string) (views.SliceView[*dri found := false var shares []*drive.Share - for i := range existingShares.Len() { - existing := existingShares.At(i) + for _, existing := range existingShares.All() { if existing.Name() != name { shares = append(shares, existing.AsStruct()) } else { diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 6a0a094d4..cd99fa351 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -511,8 +511,8 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo currentShares := b.pm.prefs.DriveShares() if currentShares.Len() > 0 { var shares []*drive.Share - for i := range currentShares.Len() { - shares = append(shares, currentShares.At(i).AsStruct()) + for _, share := range currentShares.All() { + shares = append(shares, share.AsStruct()) } fs.SetShares(shares) } @@ -6184,8 +6184,8 @@ func wireguardExitNodeDNSResolvers(nm *netmap.NetworkMap, peers map[tailcfg.Node resolvers := p.ExitNodeDNSResolvers() if !resolvers.IsNil() && resolvers.Len() > 0 { copies := make([]*dnstype.Resolver, resolvers.Len()) - for i := range resolvers.Len() { - copies[i] = resolvers.At(i).AsStruct() + for i, r := range resolvers.All() { + copies[i] = r.AsStruct() } return copies, true } diff --git a/types/views/views.go b/types/views/views.go index b99a20a48..5fe88fa6c 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -147,6 +147,17 @@ type SliceView[T ViewCloner[T, V], V StructView[T]] struct { ж []T } +// All returns an iterator over v. +func (v SliceView[T, V]) All() iter.Seq2[int, V] { + return func(yield func(int, V) bool) { + for i := range v.ж { + if !yield(i, v.ж[i].View()) { + return + } + } + } +} + // MarshalJSON implements json.Marshaler. func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } diff --git a/types/views/views_test.go b/types/views/views_test.go index 24118d099..ec7dcec4c 100644 --- a/types/views/views_test.go +++ b/types/views/views_test.go @@ -426,3 +426,35 @@ func TestSliceRange(t *testing.T) { t.Errorf("got %q; want %q", got, want) } } + +type testStruct struct{ value string } + +func (p *testStruct) Clone() *testStruct { + if p == nil { + return p + } + return &testStruct{p.value} +} +func (p *testStruct) View() testStructView { return testStructView{p} } + +type testStructView struct{ p *testStruct } + +func (v testStructView) Valid() bool { return v.p != nil } +func (v testStructView) AsStruct() *testStruct { + if v.p == nil { + return nil + } + return v.p.Clone() +} + +func TestSliceViewRange(t *testing.T) { + vs := SliceOfViews([]*testStruct{{value: "foo"}, {value: "bar"}}) + var got []string + for i, v := range vs.All() { + got = append(got, fmt.Sprintf("%d-%s", i, v.AsStruct().value)) + } + want := []string{"0-foo", "1-bar"} + if !slices.Equal(got, want) { + t.Errorf("got %q; want %q", got, want) + } +}