mirror of https://github.com/tailscale/tailscale/
types/opt: add opt package for a new opt.Bool JSON type
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>pull/105/head
parent
c185e6b4b0
commit
a07af762e4
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package opt defines optional types.
|
||||||
|
package opt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bool represents an optional boolean to be JSON-encoded.
|
||||||
|
// The string can be empty (for unknown or unspecified), or
|
||||||
|
// "true" or "false".
|
||||||
|
type Bool string
|
||||||
|
|
||||||
|
func (b *Bool) Set(v bool) {
|
||||||
|
*b = Bool(strconv.FormatBool(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bool) Clear() { *b = "" }
|
||||||
|
|
||||||
|
func (b Bool) Get() (v bool, ok bool) {
|
||||||
|
if b == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseBool(string(b))
|
||||||
|
return v, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
nullBytes = []byte("null")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b Bool) MarshalJSON() ([]byte, error) {
|
||||||
|
switch b {
|
||||||
|
case "true":
|
||||||
|
return trueBytes, nil
|
||||||
|
case "false":
|
||||||
|
return falseBytes, nil
|
||||||
|
case "":
|
||||||
|
return nullBytes, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("invalid opt.Bool value %q", string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bool) UnmarshalJSON(j []byte) error {
|
||||||
|
// Note: written with a bunch of ifs instead of a switch
|
||||||
|
// because I'm sure the Go compiler optimizes away these
|
||||||
|
// []byte->string allocations in an == comparison, but I'm too
|
||||||
|
// lazy to check whether that's true in a switch also.
|
||||||
|
if string(j) == "true" {
|
||||||
|
*b = "true"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if string(j) == "false" {
|
||||||
|
*b = "false"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if string(j) == "null" {
|
||||||
|
*b = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid opt.Bool value %q", j)
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package opt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBool(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in interface{}
|
||||||
|
want string // JSON
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "null_for_unset",
|
||||||
|
in: struct {
|
||||||
|
True Bool
|
||||||
|
False Bool
|
||||||
|
Unset Bool
|
||||||
|
}{
|
||||||
|
True: "true",
|
||||||
|
False: "false",
|
||||||
|
},
|
||||||
|
want: `{"True":true,"False":false,"Unset":null}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "omitempty_unset",
|
||||||
|
in: struct {
|
||||||
|
True Bool
|
||||||
|
False Bool
|
||||||
|
Unset Bool `json:",omitempty"`
|
||||||
|
}{
|
||||||
|
True: "true",
|
||||||
|
False: "false",
|
||||||
|
},
|
||||||
|
want: `{"True":true,"False":false}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
j, err := json.Marshal(tt.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(j) != tt.want {
|
||||||
|
t.Errorf("wrong JSON:\n got: %s\nwant: %s\n", j, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And back again:
|
||||||
|
newVal := reflect.New(reflect.TypeOf(tt.in))
|
||||||
|
out := newVal.Interface()
|
||||||
|
if err := json.Unmarshal(j, out); err != nil {
|
||||||
|
t.Fatalf("Unmarshal %#q: %v", j, err)
|
||||||
|
}
|
||||||
|
got := newVal.Elem().Interface()
|
||||||
|
if !reflect.DeepEqual(tt.in, got) {
|
||||||
|
t.Errorf("value mismatch\n got: %+v\nwant: %+v\n", got, tt.in)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue