Compare commits
3 Commits
dependabot
...
icio/views
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f3bf335e4 | ||
|
|
c0e50ebf7f | ||
|
|
09285ead78 |
@@ -28,7 +28,7 @@ EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tags=""
|
||||
tags="ts_omit_jsonv2"
|
||||
ldflags="-X tailscale.com/version.longStamp=${VERSION_LONG} -X tailscale.com/version.shortStamp=${VERSION_SHORT}"
|
||||
|
||||
# build_dist.sh arguments must precede go build arguments.
|
||||
|
||||
@@ -14,7 +14,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt+
|
||||
github.com/go-json-experiment/json from tailscale.com/types/views+
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||
|
||||
@@ -100,7 +100,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
github.com/gaissmai/bart from tailscale.com/net/ipset+
|
||||
github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+
|
||||
github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt+
|
||||
github.com/go-json-experiment/json from tailscale.com/types/views+
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext+
|
||||
|
||||
@@ -2,7 +2,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
|
||||
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
||||
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt
|
||||
github.com/go-json-experiment/json from tailscale.com/types/views+
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||
|
||||
@@ -9,7 +9,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/pe+
|
||||
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt+
|
||||
github.com/go-json-experiment/json from tailscale.com/types/views+
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json+
|
||||
|
||||
@@ -94,7 +94,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
github.com/gaissmai/bart from tailscale.com/net/tstun+
|
||||
github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+
|
||||
github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart
|
||||
github.com/go-json-experiment/json from tailscale.com/types/opt+
|
||||
github.com/go-json-experiment/json from tailscale.com/types/views+
|
||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+
|
||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+
|
||||
github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext+
|
||||
|
||||
@@ -42,7 +42,7 @@ func (v {{.ViewName}}{{.TypeParamNames}}) Valid() bool { return v.ж != nil }
|
||||
|
||||
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||
// the original.
|
||||
func (v {{.ViewName}}{{.TypeParamNames}}) AsStruct() *{{.StructName}}{{.TypeParamNames}}{
|
||||
func (v {{.ViewName}}{{.TypeParamNames}}) AsStruct() *{{.StructName}}{{.TypeParamNames}}{
|
||||
if v.ж == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -51,6 +51,8 @@ func (v {{.ViewName}}{{.TypeParamNames}}) AsStruct() *{{.StructName}}{{.TypePara
|
||||
|
||||
func (v {{.ViewName}}{{.TypeParamNames}}) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
{{if .JSONV2}}func (v {{.ViewName}}{{.TypeParamNames}}) MarshalJSONV2(e *jsontext.Encoder, opt jsonexpv2.Options) error { return jsonexpv2.MarshalEncode(e, v.ж, opt) }{{end}}
|
||||
|
||||
func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSON(b []byte) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
@@ -120,13 +122,17 @@ func requiresCloning(t types.Type) (shallow, deep bool, base types.Type) {
|
||||
return p, p, t
|
||||
}
|
||||
|
||||
func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *types.Package) {
|
||||
func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *types.Package, jsonv2 bool) {
|
||||
t, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok || codegen.IsViewType(t) {
|
||||
return
|
||||
}
|
||||
it.Import("encoding/json")
|
||||
it.Import("errors")
|
||||
if jsonv2 {
|
||||
it.ImportAs("github.com/go-json-experiment/json", "jsonexpv2")
|
||||
it.Import("github.com/go-json-experiment/json/jsontext")
|
||||
}
|
||||
|
||||
args := struct {
|
||||
StructName string
|
||||
@@ -145,9 +151,14 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *
|
||||
|
||||
// MakeViewFnName is the name of the function that accepts a value and returns a read-only view of it.
|
||||
MakeViewFnName string
|
||||
|
||||
// JSONV2 enables the addition of MarshalJSONV2 methods which depend on
|
||||
// github.com/go-json-experiment/json.
|
||||
JSONV2 bool
|
||||
}{
|
||||
StructName: typ.Obj().Name(),
|
||||
ViewName: typ.Origin().Obj().Name() + "View",
|
||||
JSONV2: jsonv2,
|
||||
}
|
||||
|
||||
typeParams := typ.Origin().TypeParams()
|
||||
@@ -574,6 +585,7 @@ var (
|
||||
flagTypes = flag.String("type", "", "comma-separated list of types; required")
|
||||
flagBuildTags = flag.String("tags", "", "compiler build tags to apply")
|
||||
flagCloneFunc = flag.Bool("clonefunc", false, "add a top-level Clone func")
|
||||
flagJSONV2 = flag.Bool("jsonv2", false, "add jsonv2 Marshal methods")
|
||||
|
||||
flagCloneOnlyTypes = flag.String("clone-only-type", "", "comma-separated list of types (a subset of --type) that should only generate a go:generate clone line and not actual views")
|
||||
|
||||
@@ -630,7 +642,7 @@ func main() {
|
||||
if !hasClone {
|
||||
runCloner = true
|
||||
}
|
||||
genView(buf, it, typ, pkg.Types)
|
||||
genView(buf, it, typ, pkg.Types, *flagJSONV2)
|
||||
}
|
||||
out := pkg.Name + "_view"
|
||||
if *flagBuildTags == "test" {
|
||||
|
||||
@@ -17,10 +17,12 @@ import (
|
||||
|
||||
func TestViewerImports(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
typeNames []string
|
||||
wantImports []string
|
||||
name string
|
||||
content string
|
||||
jsonv2 bool
|
||||
typeNames []string
|
||||
wantImports []string
|
||||
wantNoImports []string
|
||||
}{
|
||||
{
|
||||
name: "Map",
|
||||
@@ -34,6 +36,20 @@ func TestViewerImports(t *testing.T) {
|
||||
typeNames: []string{"Test"},
|
||||
wantImports: []string{"tailscale.com/types/views"},
|
||||
},
|
||||
{
|
||||
name: "withJSONV2",
|
||||
content: `type Test struct { }`,
|
||||
jsonv2: true,
|
||||
typeNames: []string{"Test"},
|
||||
wantImports: []string{"github.com/go-json-experiment/json"},
|
||||
},
|
||||
{
|
||||
name: "withoutJSONV2",
|
||||
content: `type Test struct { }`,
|
||||
jsonv2: false,
|
||||
typeNames: []string{"Test"},
|
||||
wantNoImports: []string{"github.com/go-json-experiment/json"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -65,7 +81,7 @@ func TestViewerImports(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatalf("%q is not a named type", tt.typeNames[i])
|
||||
}
|
||||
genView(&output, tracker, namedType, pkg)
|
||||
genView(&output, tracker, namedType, pkg, tt.jsonv2)
|
||||
}
|
||||
|
||||
for _, pkgName := range tt.wantImports {
|
||||
@@ -73,6 +89,11 @@ func TestViewerImports(t *testing.T) {
|
||||
t.Errorf("missing import %q", pkgName)
|
||||
}
|
||||
}
|
||||
for _, pkgName := range tt.wantNoImports {
|
||||
if tracker.Has(pkgName) {
|
||||
t.Errorf("unwanted import %q", pkgName)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
41
types/views/views_jsonv2.go
Normal file
41
types/views/views_jsonv2.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_jsonv2
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// MarshalJSONV2 implements jsonv2.MarshalerV2.
|
||||
func (m MapSlice[K, v]) MarshalJSONV2(e *jsontext.Encoder, opt jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(e, m.ж, opt)
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements jsonv2.MarshalerV2.
|
||||
func (m Map[K, V]) MarshalJSONV2(e *jsontext.Encoder, opt jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(e, m.ж, opt)
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements jsonv2.MarshalerV2.
|
||||
func (v ByteSlice[T]) MarshalJSONV2(e *jsontext.Encoder, opt jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(e, v.ж, opt)
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements jsonv2.MarshalerV2.
|
||||
func (v SliceView[T, V]) MarshalJSONV2(e *jsontext.Encoder, opt jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(e, v.ж, opt)
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements jsonv2.MarshalerV2.
|
||||
func (v Slice[T]) MarshalJSONV2(e *jsontext.Encoder, opt jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(e, v.ж, opt)
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements jsonv2.MarshalerV2.
|
||||
func (p ValuePointer[T]) MarshalJSONV2(e *jsontext.Encoder, opt jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(e, p.ж, opt)
|
||||
}
|
||||
85
types/views/views_jsonv2_test.go
Normal file
85
types/views/views_jsonv2_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_jsonv2
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestByteSlice_MarshalJSONV2(t *testing.T) {
|
||||
compareJSONv1v2(t, ByteSliceOf([]byte{}))
|
||||
compareJSONv1v2(t, ByteSliceOf(alwaysMarshalSliceV1[byte]([]byte{255})))
|
||||
}
|
||||
|
||||
func TestSliceView_MarshalJSONV2(t *testing.T) {
|
||||
compareJSONv1v2(t, SliceOfViews([]*testobj{
|
||||
{1},
|
||||
{"a"},
|
||||
{alwaysMarshalSliceV1[bool]([]bool{true, false})},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestSlice_MarshalJSONV2(t *testing.T) {
|
||||
compareJSONv1v2(t, SliceOf([]int{1, 2, 3}))
|
||||
compareJSONv1v2(t, SliceOf(alwaysMarshalSliceV1[int]([]int{4, 5, 6})))
|
||||
}
|
||||
|
||||
func TestMapSlice_MarshalJSONV2(t *testing.T) {
|
||||
compareJSONv1v2(t, MapSliceOf(map[string][]int{"a": {1, 2, 3}}))
|
||||
compareJSONv1v2(t, MapSliceOf(map[string][]alwaysMarshalSliceV1[int]{"a": {{1, 2, 3}}}))
|
||||
}
|
||||
|
||||
func TestMap_MarshalJSONV2(t *testing.T) {
|
||||
compareJSONv1v2(t, MapOf(map[string]int{"a": 1, "b": 2}))
|
||||
compareJSONv1v2(t, MapOf(map[string]alwaysMarshalSliceV1[int]{"a": {1, 2, 3}}))
|
||||
}
|
||||
|
||||
func TestValuePointer_MarshalJSONV2(t *testing.T) {
|
||||
compareJSONv1v2(t, ValuePointerOf(&testobj{}))
|
||||
compareJSONv1v2(t, ValuePointerOf(&alwaysMarshalSliceV1[int]{1, 2, 3}))
|
||||
}
|
||||
|
||||
type testobj struct{ V any }
|
||||
|
||||
func (o *testobj) Clone() *testobj { return o }
|
||||
func (o *testobj) View() testobjView { return testobjView{o} }
|
||||
|
||||
type testobjView struct{ O *testobj }
|
||||
|
||||
func (v testobjView) Valid() bool { return v.O != nil }
|
||||
func (v testobjView) AsStruct() *testobj { return v.O }
|
||||
|
||||
var (
|
||||
_ ViewCloner[*testobj, testobjView] = (*testobj)(nil)
|
||||
_ StructView[*testobj] = testobjView{}
|
||||
)
|
||||
|
||||
type alwaysMarshalSliceV1[T any] []T
|
||||
|
||||
func (m alwaysMarshalSliceV1[T]) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"v1"`), nil
|
||||
}
|
||||
|
||||
func compareJSONv1v2(t testing.TB, v any) {
|
||||
t.Helper()
|
||||
b1, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal(%T) failed: %s", v, err)
|
||||
}
|
||||
b2, err := jsonv2.Marshal(v, jsonv2.Deterministic(true))
|
||||
if err != nil {
|
||||
t.Fatalf("jsonv2.Marshal(%T) failed: %s", v, err)
|
||||
}
|
||||
t.Logf("%T:\nv1: %s\nv2: %s", v, b1, b2)
|
||||
|
||||
if d := cmp.Diff(string(b1), string(b2)); d != "" {
|
||||
t.Fatalf("json %T diff (-v1 +v2)\n%s", v, d)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package codegen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
@@ -88,27 +89,34 @@ func NewImportTracker(thisPkg *types.Package) *ImportTracker {
|
||||
// ImportTracker provides a mechanism to track and build import paths.
|
||||
type ImportTracker struct {
|
||||
thisPkg *types.Package
|
||||
packages map[string]bool
|
||||
packages map[string]string // "github.com/go-json-experiment/json" => "jsonv2", or "encoding/json" => "" (default to basename "json").
|
||||
}
|
||||
|
||||
func (it *ImportTracker) Import(pkg string) {
|
||||
if pkg != "" && !it.packages[pkg] {
|
||||
mak.Set(&it.packages, pkg, true)
|
||||
if pkg != "" && !it.Has(pkg) {
|
||||
mak.Set(&it.packages, pkg, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (it *ImportTracker) ImportAs(pkg, as string) {
|
||||
if pkg != "" && !it.Has(pkg) {
|
||||
mak.Set(&it.packages, pkg, as)
|
||||
}
|
||||
}
|
||||
|
||||
// Has reports whether the specified package has been imported.
|
||||
func (it *ImportTracker) Has(pkg string) bool {
|
||||
return it.packages[pkg]
|
||||
_, ok := it.packages[pkg]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (it *ImportTracker) qualifier(pkg *types.Package) string {
|
||||
if it.thisPkg == pkg {
|
||||
return ""
|
||||
}
|
||||
it.Import(pkg.Path())
|
||||
// TODO(maisem): handle conflicts?
|
||||
return pkg.Name()
|
||||
path := pkg.Path()
|
||||
it.Import(path)
|
||||
return cmp.Or(it.packages[path], pkg.Name())
|
||||
}
|
||||
|
||||
// QualifiedName returns the string representation of t in the package.
|
||||
@@ -127,8 +135,12 @@ func (it *ImportTracker) PackagePrefix(pkg *types.Package) string {
|
||||
// Write prints all the tracked imports in a single import block to w.
|
||||
func (it *ImportTracker) Write(w io.Writer) {
|
||||
fmt.Fprintf(w, "import (\n")
|
||||
for s := range it.packages {
|
||||
fmt.Fprintf(w, "\t%q\n", s)
|
||||
for s, q := range it.packages {
|
||||
if q != "" {
|
||||
fmt.Fprintf(w, "\t%s %q\n", q, s)
|
||||
} else {
|
||||
fmt.Fprintf(w, "\t%q\n", s)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, ")\n\n")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user