types/views: add generic Slice[T] and remove StringSlice

Also make IPPrefixSliceOf use Slice[netaddr.IPPrefix] as it also
provides additional functions besides the standard ones provided by
Slice[T].

Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
Maisem Ali 2022-03-16 21:45:19 -07:00 committed by Maisem Ali
parent d8953bf2ba
commit 6b9d938c1a
5 changed files with 48 additions and 52 deletions

View File

@ -444,10 +444,10 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
exitNodeOption := tsaddr.PrefixesContainsFunc(p.AllowedIPs, func(r netaddr.IPPrefix) bool { exitNodeOption := tsaddr.PrefixesContainsFunc(p.AllowedIPs, func(r netaddr.IPPrefix) bool {
return r.Bits() == 0 return r.Bits() == 0
}) })
var tags *views.StringSlice var tags *views.Slice[string]
var primaryRoutes *views.IPPrefixSlice var primaryRoutes *views.IPPrefixSlice
if p.Tags != nil { if p.Tags != nil {
v := views.StringSliceOf(p.Tags) v := views.SliceOf(p.Tags)
tags = &v tags = &v
} }
if p.PrimaryRoutes != nil { if p.PrimaryRoutes != nil {

View File

@ -112,7 +112,7 @@ type PeerStatus struct {
// Tags are the list of ACL tags applied to this node. // Tags are the list of ACL tags applied to this node.
// See tailscale.com/tailcfg#Node.Tags for more information. // See tailscale.com/tailcfg#Node.Tags for more information.
Tags *views.StringSlice `json:",omitempty"` Tags *views.Slice[string] `json:",omitempty"`
// PrimaryRoutes are the routes this node is currently the primary // PrimaryRoutes are the routes this node is currently the primary
// subnet router for, as determined by the control plane. It does // subnet router for, as determined by the control plane. It does

View File

@ -527,12 +527,12 @@ func (v HostinfoView) RoutableIPs() views.IPPrefixSlice {
return views.IPPrefixSliceOf(v.ж.RoutableIPs) return views.IPPrefixSliceOf(v.ж.RoutableIPs)
} }
func (v HostinfoView) RequestTags() views.StringSlice { func (v HostinfoView) RequestTags() views.Slice[string] {
return views.StringSliceOf(v.ж.RequestTags) return views.SliceOf(v.ж.RequestTags)
} }
func (v HostinfoView) SSH_HostKeys() views.StringSlice { func (v HostinfoView) SSH_HostKeys() views.Slice[string] {
return views.StringSliceOf(v.ж.SSH_HostKeys) return views.SliceOf(v.ж.SSH_HostKeys)
} }
func (v HostinfoView) Services() ServiceSlice { func (v HostinfoView) Services() ServiceSlice {

View File

@ -14,25 +14,25 @@
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
) )
// StringSlice is a read-only accessor for a slice of strings. // Slice is a read-only accessor for a slice.
type StringSlice struct { type Slice[T any] struct {
// It is named distinctively to make you think of how dangerous it is to escape // It is named distinctively to make you think of how dangerous it is to escape
// to callers. You must not let callers be able to mutate it. // to callers. You must not let callers be able to mutate it.
ж []string ж []T
} }
// StringSliceOf returns a StringSlice for the provided slice. // SliceOf returns a Slice for the provided slice.
func StringSliceOf(x []string) StringSlice { return StringSlice{x} } func SliceOf[T any](x []T) Slice[T] { return Slice[T]{x} }
// MarshalJSON implements json.Marshaler. // MarshalJSON implements json.Marshaler.
func (v StringSlice) MarshalJSON() ([]byte, error) { func (v Slice[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(v.ж) return json.Marshal(v.ж)
} }
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (v *StringSlice) UnmarshalJSON(b []byte) error { func (v *Slice[T]) UnmarshalJSON(b []byte) error {
if v.ж != nil { if v.ж != nil {
return errors.New("StringSlice is already initialized") return errors.New("Slice is already initialized")
} }
if len(b) == 0 { if len(b) == 0 {
return nil return nil
@ -44,83 +44,77 @@ func (v *StringSlice) UnmarshalJSON(b []byte) error {
} }
// IsNil reports whether the underlying slice is nil. // IsNil reports whether the underlying slice is nil.
func (v StringSlice) IsNil() bool { return v.ж == nil } func (v Slice[T]) IsNil() bool { return v.ж == nil }
// Len returns the length of the slice. // Len returns the length of the slice.
func (v StringSlice) Len() int { return len(v.ж) } func (v Slice[T]) Len() int { return len(v.ж) }
// At returns the string at index `i` of the slice. // At returns the element at index `i` of the slice.
func (v StringSlice) At(i int) string { return v.ж[i] } func (v Slice[T]) At(i int) T { return v.ж[i] }
// AppendTo appends the underlying slice values to dst. // AppendTo appends the underlying slice values to dst.
func (v StringSlice) AppendTo(dst []string) []string { func (v Slice[T]) AppendTo(dst []T) []T {
return append(dst, v.ж...) return append(dst, v.ж...)
} }
// AsSlice returns a copy of underlying slice. // AsSlice returns a copy of underlying slice.
func (v StringSlice) AsSlice() []string { func (v Slice[T]) AsSlice() []T {
return v.AppendTo(v.ж[:0:0]) return v.AppendTo(v.ж[:0:0])
} }
// IPPrefixSlice is a read-only accessor for a slice of netaddr.IPPrefix. // IPPrefixSlice is a read-only accessor for a slice of netaddr.IPPrefix.
type IPPrefixSlice struct { type IPPrefixSlice struct {
// It is named distinctively to make you think of how dangerous it is to escape ж Slice[netaddr.IPPrefix]
// to callers. You must not let callers be able to mutate it.jd
ж []netaddr.IPPrefix
} }
// IPPrefixSliceOf returns a IPPrefixSlice for the provided slice. // IPPrefixSliceOf returns a IPPrefixSlice for the provided slice.
func IPPrefixSliceOf(x []netaddr.IPPrefix) IPPrefixSlice { return IPPrefixSlice{x} } func IPPrefixSliceOf(x []netaddr.IPPrefix) IPPrefixSlice { return IPPrefixSlice{SliceOf(x)} }
// IsNil reports whether the underlying slice is nil. // IsNil reports whether the underlying slice is nil.
func (v IPPrefixSlice) IsNil() bool { return v.ж == nil } func (v IPPrefixSlice) IsNil() bool { return v.ж.IsNil() }
// Len returns the length of the slice. // Len returns the length of the slice.
func (v IPPrefixSlice) Len() int { return len(v.ж) } func (v IPPrefixSlice) Len() int { return v.ж.Len() }
// At returns the IPPrefix at index `i` of the slice. // At returns the IPPrefix at index `i` of the slice.
func (v IPPrefixSlice) At(i int) netaddr.IPPrefix { return v.ж[i] } func (v IPPrefixSlice) At(i int) netaddr.IPPrefix { return v.ж.At(i) }
// Append appends the underlying slice values to dst. // AppendTo appends the underlying slice values to dst.
func (v IPPrefixSlice) AppendTo(dst []netaddr.IPPrefix) []netaddr.IPPrefix { func (v IPPrefixSlice) AppendTo(dst []netaddr.IPPrefix) []netaddr.IPPrefix {
return append(dst, v.ж...) return v.ж.AppendTo(dst)
}
// Generic returns the underlying Slice[netaddr.IPPrefix].
func (v IPPrefixSlice) Generic() Slice[netaddr.IPPrefix] {
return v.ж
} }
// AsSlice returns a copy of underlying slice. // AsSlice returns a copy of underlying slice.
func (v IPPrefixSlice) AsSlice() []netaddr.IPPrefix { func (v IPPrefixSlice) AsSlice() []netaddr.IPPrefix {
return v.AppendTo(v.ж[:0:0]) return v.ж.AsSlice()
} }
// PrefixesContainsIP reports whether any IPPrefix contains IP. // PrefixesContainsIP reports whether any IPPrefix contains IP.
func (v IPPrefixSlice) ContainsIP(ip netaddr.IP) bool { func (v IPPrefixSlice) ContainsIP(ip netaddr.IP) bool {
return tsaddr.PrefixesContainsIP(v.ж, ip) return tsaddr.PrefixesContainsIP(v.ж.ж, ip)
} }
// PrefixesContainsFunc reports whether f is true for any IPPrefix in the slice. // PrefixesContainsFunc reports whether f is true for any IPPrefix in the slice.
func (v IPPrefixSlice) ContainsFunc(f func(netaddr.IPPrefix) bool) bool { func (v IPPrefixSlice) ContainsFunc(f func(netaddr.IPPrefix) bool) bool {
return tsaddr.PrefixesContainsFunc(v.ж, f) return tsaddr.PrefixesContainsFunc(v.ж.ж, f)
} }
// ContainsExitRoutes reports whether v contains ExitNode Routes. // ContainsExitRoutes reports whether v contains ExitNode Routes.
func (v IPPrefixSlice) ContainsExitRoutes() bool { func (v IPPrefixSlice) ContainsExitRoutes() bool {
return tsaddr.ContainsExitRoutes(v.ж) return tsaddr.ContainsExitRoutes(v.ж.ж)
} }
// MarshalJSON implements json.Marshaler. // MarshalJSON implements json.Marshaler.
func (v IPPrefixSlice) MarshalJSON() ([]byte, error) { func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
return json.Marshal(v.ж) return v.ж.MarshalJSON()
} }
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (v *IPPrefixSlice) UnmarshalJSON(b []byte) error { func (v *IPPrefixSlice) UnmarshalJSON(b []byte) error {
if v.ж != nil { return v.ж.UnmarshalJSON(b)
return errors.New("IPPrefixSlice is already initialized")
}
if len(b) == 0 {
return nil
}
if err := json.Unmarshal(b, &v.ж); err != nil {
return err
}
return nil
} }

View File

@ -23,10 +23,12 @@ func TestViewsJSON(t *testing.T) {
} }
type viewStruct struct { type viewStruct struct {
Addrs IPPrefixSlice Addrs IPPrefixSlice
Strings StringSlice Strings Slice[string]
AddrsPtr *IPPrefixSlice `json:",omitempty"` AddrsPtr *IPPrefixSlice `json:",omitempty"`
StringsPtr *StringSlice `json:",omitempty"` StringsPtr *Slice[string] `json:",omitempty"`
} }
ipp := IPPrefixSliceOf(mustCIDR("192.168.0.0/24"))
ss := SliceOf([]string{"bar"})
tests := []struct { tests := []struct {
name string name string
in viewStruct in viewStruct
@ -40,12 +42,12 @@ type viewStruct struct {
{ {
name: "everything", name: "everything",
in: viewStruct{ in: viewStruct{
Addrs: IPPrefixSliceOf(mustCIDR("192.168.0.0/24")), Addrs: ipp,
AddrsPtr: &IPPrefixSlice{mustCIDR("192.168.0.0/24")}, AddrsPtr: &ipp,
StringsPtr: &StringSlice{[]string{"foo"}}, StringsPtr: &ss,
Strings: StringSlice{[]string{"bar"}}, Strings: ss,
}, },
wantJSON: `{"Addrs":["192.168.0.0/24"],"Strings":["bar"],"AddrsPtr":["192.168.0.0/24"],"StringsPtr":["foo"]}`, wantJSON: `{"Addrs":["192.168.0.0/24"],"Strings":["bar"],"AddrsPtr":["192.168.0.0/24"],"StringsPtr":["bar"]}`,
}, },
} }