mirror of
https://github.com/tailscale/tailscale.git
synced 2025-05-05 23:21:00 +00:00
tailcfg: add RawMessage
This adds a new RawMessage type backed by string instead of the json.RawMessage which is backed by []byte. The byte slice makes the generated views be a lot more defensive than the need to be which we can get around by using a string instead. Updates #cleanup Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
258f16f84b
commit
d06b48dd0a
@ -45,15 +45,15 @@ func TestImpersonationHeaders(t *testing.T) {
|
|||||||
emailish: "foo@example.com",
|
emailish: "foo@example.com",
|
||||||
capMap: tailcfg.PeerCapMap{
|
capMap: tailcfg.PeerCapMap{
|
||||||
capabilityName: {
|
capabilityName: {
|
||||||
[]byte(`{"impersonate":{"groups":["group1","group2"]}}`),
|
tailcfg.RawMessage(`{"impersonate":{"groups":["group1","group2"]}}`),
|
||||||
[]byte(`{"impersonate":{"groups":["group1","group3"]}}`), // One group is duplicated.
|
tailcfg.RawMessage(`{"impersonate":{"groups":["group1","group3"]}}`), // One group is duplicated.
|
||||||
[]byte(`{"impersonate":{"groups":["group4"]}}`),
|
tailcfg.RawMessage(`{"impersonate":{"groups":["group4"]}}`),
|
||||||
[]byte(`{"impersonate":{"groups":["group2"]}}`), // duplicate
|
tailcfg.RawMessage(`{"impersonate":{"groups":["group2"]}}`), // duplicate
|
||||||
|
|
||||||
// These should be ignored, but should parse correctly.
|
// These should be ignored, but should parse correctly.
|
||||||
[]byte(`{}`),
|
tailcfg.RawMessage(`{}`),
|
||||||
[]byte(`{"impersonate":{}}`),
|
tailcfg.RawMessage(`{"impersonate":{}}`),
|
||||||
[]byte(`{"impersonate":{"groups":[]}}`),
|
tailcfg.RawMessage(`{"impersonate":{"groups":[]}}`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantHeaders: http.Header{
|
wantHeaders: http.Header{
|
||||||
@ -67,7 +67,7 @@ func TestImpersonationHeaders(t *testing.T) {
|
|||||||
tags: []string{"tag:foo", "tag:bar"},
|
tags: []string{"tag:foo", "tag:bar"},
|
||||||
capMap: tailcfg.PeerCapMap{
|
capMap: tailcfg.PeerCapMap{
|
||||||
capabilityName: {
|
capabilityName: {
|
||||||
[]byte(`{"impersonate":{"groups":["group1"]}}`),
|
tailcfg.RawMessage(`{"impersonate":{"groups":["group1"]}}`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantHeaders: http.Header{
|
wantHeaders: http.Header{
|
||||||
@ -81,7 +81,7 @@ func TestImpersonationHeaders(t *testing.T) {
|
|||||||
tags: []string{"tag:foo", "tag:bar"},
|
tags: []string{"tag:foo", "tag:bar"},
|
||||||
capMap: tailcfg.PeerCapMap{
|
capMap: tailcfg.PeerCapMap{
|
||||||
capabilityName: {
|
capabilityName: {
|
||||||
[]byte(`[]`),
|
tailcfg.RawMessage(`[]`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantHeaders: http.Header{},
|
wantHeaders: http.Header{},
|
||||||
|
@ -216,6 +216,31 @@ func (emptyStructJSONSlice) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func (emptyStructJSONSlice) UnmarshalJSON([]byte) error { return nil }
|
func (emptyStructJSONSlice) UnmarshalJSON([]byte) error { return nil }
|
||||||
|
|
||||||
|
// RawMessage is a raw encoded JSON value. It implements Marshaler and
|
||||||
|
// Unmarshaler and can be used to delay JSON decoding or precompute a JSON
|
||||||
|
// encoding.
|
||||||
|
//
|
||||||
|
// It is like json.RawMessage but is a string instead of a []byte to better
|
||||||
|
// portray immutable data.
|
||||||
|
type RawMessage string
|
||||||
|
|
||||||
|
// MarshalJSON returns m as the JSON encoding of m.
|
||||||
|
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
|
if m == "" {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
return []byte(m), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON sets *m to a copy of data.
|
||||||
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||||
|
if m == nil {
|
||||||
|
return errors.New("RawMessage: UnmarshalJSON on nil pointer")
|
||||||
|
}
|
||||||
|
*m = RawMessage(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
ID NodeID
|
ID NodeID
|
||||||
StableID StableNodeID
|
StableID StableNodeID
|
||||||
@ -1256,7 +1281,7 @@ const (
|
|||||||
//
|
//
|
||||||
// The values are opaque to Tailscale, but are passed through from the ACLs to
|
// The values are opaque to Tailscale, but are passed through from the ACLs to
|
||||||
// the application via the WhoIs API.
|
// the application via the WhoIs API.
|
||||||
type PeerCapMap map[PeerCapability][]json.RawMessage
|
type PeerCapMap map[PeerCapability][]RawMessage
|
||||||
|
|
||||||
// UnmarshalCapJSON unmarshals each JSON value in cm[cap] as T.
|
// UnmarshalCapJSON unmarshals each JSON value in cm[cap] as T.
|
||||||
// If cap does not exist in cm, it returns (nil, nil).
|
// If cap does not exist in cm, it returns (nil, nil).
|
||||||
@ -1269,7 +1294,7 @@ func UnmarshalCapJSON[T any](cm PeerCapMap, cap PeerCapability) ([]T, error) {
|
|||||||
out := make([]T, 0, len(vals))
|
out := make([]T, 0, len(vals))
|
||||||
for _, v := range vals {
|
for _, v := range vals {
|
||||||
var t T
|
var t T
|
||||||
if err := json.Unmarshal(v, &t); err != nil {
|
if err := json.Unmarshal([]byte(v), &t); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, t)
|
out = append(out, t)
|
||||||
|
@ -726,3 +726,82 @@ func TestUnmarshalHealth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRawMessage(t *testing.T) {
|
||||||
|
// Create a few types of json.RawMessages and then marshal them back and
|
||||||
|
// forth to make sure they round-trip.
|
||||||
|
|
||||||
|
type rule struct {
|
||||||
|
Ports []int `json:",omitempty"`
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
val map[string][]rule
|
||||||
|
wire map[string][]RawMessage
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
val: nil,
|
||||||
|
wire: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
val: map[string][]rule{},
|
||||||
|
wire: map[string][]RawMessage{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one",
|
||||||
|
val: map[string][]rule{
|
||||||
|
"foo": {{Ports: []int{1, 2, 3}}},
|
||||||
|
},
|
||||||
|
wire: map[string][]RawMessage{
|
||||||
|
"foo": {
|
||||||
|
`{"Ports":[1,2,3]}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "many",
|
||||||
|
val: map[string][]rule{
|
||||||
|
"foo": {{Ports: []int{1, 2, 3}}},
|
||||||
|
"bar": {{Ports: []int{4, 5, 6}}, {Ports: []int{7, 8, 9}}},
|
||||||
|
"baz": nil,
|
||||||
|
"abc": {},
|
||||||
|
"def": {{}},
|
||||||
|
},
|
||||||
|
wire: map[string][]RawMessage{
|
||||||
|
"foo": {
|
||||||
|
`{"Ports":[1,2,3]}`,
|
||||||
|
},
|
||||||
|
"bar": {
|
||||||
|
`{"Ports":[4,5,6]}`,
|
||||||
|
`{"Ports":[7,8,9]}`,
|
||||||
|
},
|
||||||
|
"baz": nil,
|
||||||
|
"abc": {},
|
||||||
|
"def": {"{}"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
j := must.Get(json.Marshal(tc.val))
|
||||||
|
var gotWire map[string][]RawMessage
|
||||||
|
if err := json.Unmarshal(j, &gotWire); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotWire, tc.wire) {
|
||||||
|
t.Errorf("got %#v; want %#v", gotWire, tc.wire)
|
||||||
|
}
|
||||||
|
|
||||||
|
j = must.Get(json.Marshal(tc.wire))
|
||||||
|
var gotVal map[string][]rule
|
||||||
|
if err := json.Unmarshal(j, &gotVal); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotVal, tc.val) {
|
||||||
|
t.Errorf("got %#v; want %#v", gotVal, tc.val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
package filter
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -49,12 +48,7 @@ func (src *CapMatch) Clone() *CapMatch {
|
|||||||
}
|
}
|
||||||
dst := new(CapMatch)
|
dst := new(CapMatch)
|
||||||
*dst = *src
|
*dst = *src
|
||||||
if src.Values != nil {
|
dst.Values = append(src.Values[:0:0], src.Values...)
|
||||||
dst.Values = make([]json.RawMessage, len(src.Values))
|
|
||||||
for i := range dst.Values {
|
|
||||||
dst.Values[i] = append(src.Values[i][:0:0], src.Values[i]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,5 +56,5 @@ func (src *CapMatch) Clone() *CapMatch {
|
|||||||
var _CapMatchCloneNeedsRegeneration = CapMatch(struct {
|
var _CapMatchCloneNeedsRegeneration = CapMatch(struct {
|
||||||
Dst netip.Prefix
|
Dst netip.Prefix
|
||||||
Cap tailcfg.PeerCapability
|
Cap tailcfg.PeerCapability
|
||||||
Values []json.RawMessage
|
Values []tailcfg.RawMessage
|
||||||
}{})
|
}{})
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
package filter
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
@ -60,7 +59,7 @@ type CapMatch struct {
|
|||||||
|
|
||||||
// Values are the raw JSON values of the capability.
|
// Values are the raw JSON values of the capability.
|
||||||
// See tailcfg.PeerCapability and tailcfg.PeerCapMap for details.
|
// See tailcfg.PeerCapability and tailcfg.PeerCapMap for details.
|
||||||
Values []json.RawMessage
|
Values []tailcfg.RawMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match matches packets from any IP address in Srcs to any ip:port in
|
// Match matches packets from any IP address in Srcs to any ip:port in
|
||||||
|
Loading…
x
Reference in New Issue
Block a user