summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMaisem Ali <maisem@tailscale.com>2022-05-09 09:30:39 -0700
committerMaisem Ali <maisem@gmail.com>2022-05-09 19:49:31 -0700
commitd04afc697cbb81fb765fdee8847b8e33e34537be (patch)
tree5a0b409f9f7dd7a9cae5335b3488b4ab5fbb3700
parent5cd56fe8d5f9585ef130dde4b7aa04eaf201685a (diff)
downloadtailscale-d04afc697cbb81fb765fdee8847b8e33e34537be.tar.xz
tailscale-d04afc697cbb81fb765fdee8847b8e33e34537be.zip
cmd/viewer,types/views: add support for views of maps
Updates #4635 Signed-off-by: Maisem Ali <maisem@tailscale.com>
-rw-r--r--cmd/viewer/tests/tests.go13
-rw-r--r--cmd/viewer/tests/tests_clone.go73
-rw-r--r--cmd/viewer/tests/tests_view.go50
-rw-r--r--cmd/viewer/viewer.go74
-rw-r--r--tailcfg/tailcfg.go7
-rw-r--r--tailcfg/tailcfg_view.go15
-rw-r--r--types/views/views.go103
7 files changed, 314 insertions, 21 deletions
diff --git a/cmd/viewer/tests/tests.go b/cmd/viewer/tests/tests.go
index 18115ea0f..ec202ccde 100644
--- a/cmd/viewer/tests/tests.go
+++ b/cmd/viewer/tests/tests.go
@@ -19,7 +19,18 @@ type StructWithoutPtrs struct {
}
type Map struct {
- M map[string]int
+ Int map[string]int
+ SliceInt map[string][]int
+ StructWithPtr map[string]*StructWithPtrs
+ StructWithoutPtr map[string]*StructWithoutPtrs
+ SlicesWithPtrs map[string][]*StructWithPtrs
+ SlicesWithoutPtrs map[string][]*StructWithoutPtrs
+ StructWithoutPtrKey map[StructWithoutPtrs]int `json:"-"`
+
+ // Unsupported.
+ SliceIntPtr map[string][]*int
+ PointerKey map[*string]int `json:"-"`
+ StructWithPtrKey map[StructWithPtrs]int `json:"-"`
}
type StructWithPtrs struct {
diff --git a/cmd/viewer/tests/tests_clone.go b/cmd/viewer/tests/tests_clone.go
index 1fb12746f..357fa20a5 100644
--- a/cmd/viewer/tests/tests_clone.go
+++ b/cmd/viewer/tests/tests_clone.go
@@ -61,10 +61,64 @@ func (src *Map) Clone() *Map {
}
dst := new(Map)
*dst = *src
- if dst.M != nil {
- dst.M = map[string]int{}
- for k, v := range src.M {
- dst.M[k] = v
+ if dst.Int != nil {
+ dst.Int = map[string]int{}
+ for k, v := range src.Int {
+ dst.Int[k] = v
+ }
+ }
+ if dst.SliceInt != nil {
+ dst.SliceInt = map[string][]int{}
+ for k := range src.SliceInt {
+ dst.SliceInt[k] = append([]int{}, src.SliceInt[k]...)
+ }
+ }
+ if dst.StructWithPtr != nil {
+ dst.StructWithPtr = map[string]*StructWithPtrs{}
+ for k, v := range src.StructWithPtr {
+ dst.StructWithPtr[k] = v.Clone()
+ }
+ }
+ if dst.StructWithoutPtr != nil {
+ dst.StructWithoutPtr = map[string]*StructWithoutPtrs{}
+ for k, v := range src.StructWithoutPtr {
+ dst.StructWithoutPtr[k] = v.Clone()
+ }
+ }
+ if dst.SlicesWithPtrs != nil {
+ dst.SlicesWithPtrs = map[string][]*StructWithPtrs{}
+ for k := range src.SlicesWithPtrs {
+ dst.SlicesWithPtrs[k] = append([]*StructWithPtrs{}, src.SlicesWithPtrs[k]...)
+ }
+ }
+ if dst.SlicesWithoutPtrs != nil {
+ dst.SlicesWithoutPtrs = map[string][]*StructWithoutPtrs{}
+ for k := range src.SlicesWithoutPtrs {
+ dst.SlicesWithoutPtrs[k] = append([]*StructWithoutPtrs{}, src.SlicesWithoutPtrs[k]...)
+ }
+ }
+ if dst.StructWithoutPtrKey != nil {
+ dst.StructWithoutPtrKey = map[StructWithoutPtrs]int{}
+ for k, v := range src.StructWithoutPtrKey {
+ dst.StructWithoutPtrKey[k] = v
+ }
+ }
+ if dst.SliceIntPtr != nil {
+ dst.SliceIntPtr = map[string][]*int{}
+ for k := range src.SliceIntPtr {
+ dst.SliceIntPtr[k] = append([]*int{}, src.SliceIntPtr[k]...)
+ }
+ }
+ if dst.PointerKey != nil {
+ dst.PointerKey = map[*string]int{}
+ for k, v := range src.PointerKey {
+ dst.PointerKey[k] = v
+ }
+ }
+ if dst.StructWithPtrKey != nil {
+ dst.StructWithPtrKey = map[StructWithPtrs]int{}
+ for k, v := range src.StructWithPtrKey {
+ dst.StructWithPtrKey[k] = v
}
}
return dst
@@ -72,7 +126,16 @@ func (src *Map) Clone() *Map {
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _MapCloneNeedsRegeneration = Map(struct {
- M map[string]int
+ Int map[string]int
+ SliceInt map[string][]int
+ StructWithPtr map[string]*StructWithPtrs
+ StructWithoutPtr map[string]*StructWithoutPtrs
+ SlicesWithPtrs map[string][]*StructWithPtrs
+ SlicesWithoutPtrs map[string][]*StructWithoutPtrs
+ StructWithoutPtrKey map[StructWithoutPtrs]int
+ SliceIntPtr map[string][]*int
+ PointerKey map[*string]int
+ StructWithPtrKey map[StructWithPtrs]int
}{})
// Clone makes a deep copy of StructWithSlices.
diff --git a/cmd/viewer/tests/tests_view.go b/cmd/viewer/tests/tests_view.go
index a3abe6f1e..30d6b1af3 100644
--- a/cmd/viewer/tests/tests_view.go
+++ b/cmd/viewer/tests/tests_view.go
@@ -188,9 +188,57 @@ func (v *MapView) UnmarshalJSON(b []byte) error {
return nil
}
+func (v MapView) Int() views.Map[string, int] { return views.MapOf(v.ж.Int) }
+
+func (v MapView) SliceInt() views.MapFn[string, []int, views.Slice[int]] {
+ return views.MapFnOf(v.ж.SliceInt, func(t []int) views.Slice[int] {
+ return views.SliceOf(t)
+ })
+}
+
+func (v MapView) StructWithPtr() views.MapFn[string, *StructWithPtrs, StructWithPtrsView] {
+ return views.MapFnOf(v.ж.StructWithPtr, func(t *StructWithPtrs) StructWithPtrsView {
+ return t.View()
+ })
+}
+
+func (v MapView) StructWithoutPtr() views.MapFn[string, *StructWithoutPtrs, StructWithoutPtrsView] {
+ return views.MapFnOf(v.ж.StructWithoutPtr, func(t *StructWithoutPtrs) StructWithoutPtrsView {
+ return t.View()
+ })
+}
+
+func (v MapView) SlicesWithPtrs() views.MapFn[string, []*StructWithPtrs, views.SliceView[*StructWithPtrs, StructWithPtrsView]] {
+ return views.MapFnOf(v.ж.SlicesWithPtrs, func(t []*StructWithPtrs) views.SliceView[*StructWithPtrs, StructWithPtrsView] {
+ return views.SliceOfViews[*StructWithPtrs, StructWithPtrsView](t)
+ })
+}
+
+func (v MapView) SlicesWithoutPtrs() views.MapFn[string, []*StructWithoutPtrs, views.SliceView[*StructWithoutPtrs, StructWithoutPtrsView]] {
+ return views.MapFnOf(v.ж.SlicesWithoutPtrs, func(t []*StructWithoutPtrs) views.SliceView[*StructWithoutPtrs, StructWithoutPtrsView] {
+ return views.SliceOfViews[*StructWithoutPtrs, StructWithoutPtrsView](t)
+ })
+}
+
+func (v MapView) StructWithoutPtrKey() views.Map[StructWithoutPtrs, int] {
+ return views.MapOf(v.ж.StructWithoutPtrKey)
+}
+func (v MapView) SliceIntPtr() map[string][]*int { panic("unsupported") }
+func (v MapView) PointerKey() map[*string]int { panic("unsupported") }
+func (v MapView) StructWithPtrKey() map[StructWithPtrs]int { panic("unsupported") }
+
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _MapViewNeedsRegeneration = Map(struct {
- M map[string]int
+ Int map[string]int
+ SliceInt map[string][]int
+ StructWithPtr map[string]*StructWithPtrs
+ StructWithoutPtr map[string]*StructWithoutPtrs
+ SlicesWithPtrs map[string][]*StructWithPtrs
+ SlicesWithoutPtrs map[string][]*StructWithoutPtrs
+ StructWithoutPtrKey map[StructWithoutPtrs]int
+ SliceIntPtr map[string][]*int
+ PointerKey map[*string]int
+ StructWithPtrKey map[StructWithPtrs]int
}{})
// View returns a readonly view of StructWithSlices.
diff --git a/cmd/viewer/viewer.go b/cmd/viewer/viewer.go
index ad3f02696..c9909453a 100644
--- a/cmd/viewer/viewer.go
+++ b/cmd/viewer/viewer.go
@@ -88,8 +88,12 @@ func (v *{{.ViewName}}) UnmarshalJSON(b []byte) error {
{{end}}
{{define "mapField"}}
-// Unsupported, panics.
-func(v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {panic("unsupported")}
+func(v {{.ViewName}}) {{.FieldName}}() views.Map[{{.MapKeyType}},{{.MapValueType}}] { return views.MapOf(v.ж.{{.FieldName}})}
+{{end}}
+{{define "mapFnField"}}
+func(v {{.ViewName}}) {{.FieldName}}() views.MapFn[{{.MapKeyType}},{{.MapValueType}},{{.MapValueView}}] { return views.MapFnOf(v.ж.{{.FieldName}}, func (t {{.MapValueType}}) {{.MapValueView}} {
+ return {{.MapFn}}
+})}
{{end}}
{{define "unsupportedField"}}func(v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {panic("unsupported")}
{{end}}
@@ -132,6 +136,11 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
FieldName string
FieldType string
FieldViewName string
+
+ MapKeyType string
+ MapValueType string
+ MapValueView string
+ MapFn string
}{
StructName: typ.Obj().Name(),
ViewName: typ.Obj().Name() + "View",
@@ -194,7 +203,7 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
writeTemplate("sliceField")
}
continue
- case *types.Struct:
+ case *types.Struct, *types.Named:
strucT := underlying
args.FieldType = it.QualifiedName(fieldType)
if codegen.ContainsPointers(strucT) {
@@ -204,9 +213,62 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
writeTemplate("valueField")
continue
case *types.Map:
- // TODO(maisem): support this.
- // args.FieldType = importedName(ft)
- // writeTemplate("mapField")
+ m := underlying
+ args.FieldType = it.QualifiedName(fieldType)
+ shallow, deep, key := requiresCloning(m.Key())
+ if shallow || deep {
+ writeTemplate("unsupportedField")
+ continue
+ }
+ args.MapKeyType = it.QualifiedName(key)
+ mElem := m.Elem()
+ var template string
+ switch u := mElem.(type) {
+ case *types.Basic:
+ template = "mapField"
+ args.MapValueType = it.QualifiedName(mElem)
+ case *types.Slice:
+ slice := u
+ sElem := slice.Elem()
+ switch x := sElem.(type) {
+ case *types.Basic:
+ args.MapValueView = fmt.Sprintf("views.Slice[%v]", sElem)
+ args.MapValueType = "[]" + sElem.String()
+ args.MapFn = "views.SliceOf(t)"
+ template = "mapFnField"
+ case *types.Pointer:
+ ptr := x
+ pElem := ptr.Elem()
+ switch pElem.(type) {
+ case *types.Struct, *types.Named:
+ ptrType := it.QualifiedName(ptr)
+ viewType := it.QualifiedName(pElem) + "View"
+ args.MapFn = fmt.Sprintf("views.SliceOfViews[%v,%v](t)", ptrType, viewType)
+ args.MapValueView = fmt.Sprintf("views.SliceView[%v,%v]", ptrType, viewType)
+ args.MapValueType = "[]" + ptrType
+ template = "mapFnField"
+ default:
+ template = "unsupportedField"
+ }
+ default:
+ template = "unsupportedField"
+ }
+ case *types.Pointer:
+ ptr := u
+ pElem := ptr.Elem()
+ switch pElem.(type) {
+ case *types.Struct, *types.Named:
+ args.MapValueType = it.QualifiedName(ptr)
+ args.MapValueView = it.QualifiedName(pElem) + "View"
+ args.MapFn = "t.View()"
+ template = "mapFnField"
+ default:
+ template = "unsupportedField"
+ }
+ default:
+ template = "unsupportedField"
+ }
+ writeTemplate(template)
continue
case *types.Pointer:
ptr := underlying
diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go
index b313a5ca9..b7ddb9dec 100644
--- a/tailcfg/tailcfg.go
+++ b/tailcfg/tailcfg.go
@@ -531,13 +531,6 @@ type NetInfo struct {
// Update BasicallyEqual when adding fields.
}
-// DERPLatencyForEach calls fn for each value in the DERPLatency map.
-func (v NetInfoView) DERPLatencyForEach(fn func(k string, v float64)) {
- for k, v := range v.ж.DERPLatency {
- fn(k, v)
- }
-}
-
func (ni *NetInfo) String() string {
if ni == nil {
return "NetInfo(nil)"
diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go
index a5f95457b..be466d41b 100644
--- a/tailcfg/tailcfg_view.go
+++ b/tailcfg/tailcfg_view.go
@@ -343,7 +343,9 @@ func (v NetInfoView) PMP() opt.Bool { return v.ж.PMP }
func (v NetInfoView) PCP() opt.Bool { return v.ж.PCP }
func (v NetInfoView) PreferredDERP() int { return v.ж.PreferredDERP }
func (v NetInfoView) LinkType() string { return v.ж.LinkType }
-func (v NetInfoView) String() string { return v.ж.String() }
+
+func (v NetInfoView) DERPLatency() views.Map[string, float64] { return views.MapOf(v.ж.DERPLatency) }
+func (v NetInfoView) String() string { return v.ж.String() }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _NetInfoViewNeedsRegeneration = NetInfo(struct {
@@ -471,6 +473,12 @@ func (v *DNSConfigView) UnmarshalJSON(b []byte) error {
func (v DNSConfigView) Resolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.Resolvers)
}
+
+func (v DNSConfigView) Routes() views.MapFn[string, []*dnstype.Resolver, views.SliceView[*dnstype.Resolver, dnstype.ResolverView]] {
+ return views.MapFnOf(v.ж.Routes, func(t []*dnstype.Resolver) views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
+ return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](t)
+ })
+}
func (v DNSConfigView) FallbackResolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.FallbackResolvers)
}
@@ -667,6 +675,11 @@ func (v *DERPMapView) UnmarshalJSON(b []byte) error {
return nil
}
+func (v DERPMapView) Regions() views.MapFn[int, *DERPRegion, DERPRegionView] {
+ return views.MapFnOf(v.ж.Regions, func(t *DERPRegion) DERPRegionView {
+ return t.View()
+ })
+}
func (v DERPMapView) OmitDefaultRegions() bool { return v.ж.OmitDefaultRegions }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
diff --git a/types/views/views.go b/types/views/views.go
index af037f0a0..466462460 100644
--- a/types/views/views.go
+++ b/types/views/views.go
@@ -53,6 +53,8 @@ func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
// SliceView is a read-only wrapper around a struct which should only be exposed
// as a View.
type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
+ // ж is the underlying mutable value, named with a hard-to-type
+ // character that looks pointy like a pointer.
// 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.
ж []T
@@ -88,6 +90,8 @@ func (v SliceView[T, V]) AsSlice() []V {
// Slice is a read-only accessor for a slice.
type Slice[T any] struct {
+ // ж is the underlying mutable value, named with a hard-to-type
+ // character that looks pointy like a pointer.
// 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.
ж []T
@@ -181,3 +185,102 @@ func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
func (v *IPPrefixSlice) UnmarshalJSON(b []byte) error {
return v.ж.UnmarshalJSON(b)
}
+
+// MapOf returns a read-only view over m.
+func MapOf[K comparable, V comparable](m map[K]V) Map[K, V] {
+ return Map[K, V]{m}
+}
+
+// Map is a read-only accessor over a map whose values are immutable.
+type Map[K comparable, V any] struct {
+ // ж is the underlying mutable value, named with a hard-to-type
+ // character that looks pointy like a pointer.
+ // 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.
+ ж map[K]V
+}
+
+// Has reports whether k has an entry in the map.
+func (m Map[K, V]) Has(k K) bool {
+ _, ok := m.ж[k]
+ return ok
+}
+
+// IsNil reports whether the underlying map is nil.
+func (m Map[K, V]) IsNil() bool {
+ return m.ж == nil
+}
+
+// Len returns the number of elements in the map.
+func (m Map[K, V]) Len() int { return len(m.ж) }
+
+// Get returns the element with key k.
+func (m Map[K, V]) Get(k K) V {
+ return m.ж[k]
+}
+
+// GetOk returns the element with key k and a bool representing whether the key
+// is in map.
+func (m Map[K, V]) GetOk(k K) (V, bool) {
+ v, ok := m.ж[k]
+ return v, ok
+}
+
+// ForEach calls f for every k,v pair in the underlying map.
+func (m Map[K, V]) ForEach(f func(k K, v V)) {
+ for k, v := range m.ж {
+ f(k, v)
+ }
+}
+
+// MapFnOf returns a MapFn for m.
+func MapFnOf[K comparable, T any, V any](m map[K]T, f func(T) V) MapFn[K, T, V] {
+ return MapFn[K, T, V]{
+ ж: m,
+ wrapv: f,
+ }
+}
+
+// MapFn is like Map but with a func to convert values from T to V.
+// It is used to provide map of slices and views.
+type MapFn[K comparable, T any, V any] struct {
+ // ж is the underlying mutable value, named with a hard-to-type
+ // character that looks pointy like a pointer.
+ // 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.
+ ж map[K]T
+ wrapv func(T) V
+}
+
+// Has reports whether k has an entry in the map.
+func (m MapFn[K, T, V]) Has(k K) bool {
+ _, ok := m.ж[k]
+ return ok
+}
+
+// Get returns the element with key k.
+func (m MapFn[K, T, V]) Get(k K) V {
+ return m.wrapv(m.ж[k])
+}
+
+// IsNil reports whether the underlying map is nil.
+func (m MapFn[K, T, V]) IsNil() bool {
+ return m.ж == nil
+}
+
+// Len returns the number of elements in the map.
+func (m MapFn[K, T, V]) Len() int { return len(m.ж) }
+
+// GetOk returns the element with key k and a bool representing whether the key
+// is in map.
+func (m MapFn[K, T, V]) GetOk(k K) (V, bool) {
+ v, ok := m.ж[k]
+ return m.wrapv(v), ok
+}
+
+// ForEach calls f for every k,v pair in the underlying map.
+func (m MapFn[K, T, V]) ForEach(f func(k K, v V)) {
+ for k, v := range m.ж {
+ f(k, m.wrapv(v))
+ }
+}