fs: add a tristate true/false/unset configuration value

This commit is contained in:
Nick Craig-Wood 2021-11-03 20:17:15 +00:00
parent 1e4ef4b4d5
commit 9218a3eb00
2 changed files with 161 additions and 0 deletions

74
fs/tristate.go Normal file
View file

@ -0,0 +1,74 @@
package fs
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
)
// Tristate is a boolean that can has the states, true, false and
// unset/invalid/nil
type Tristate struct {
Value bool
Valid bool
}
// String renders the tristate as true/false/unset
func (t Tristate) String() string {
if !t.Valid {
return "unset"
}
if t.Value {
return "true"
}
return "false"
}
// Set the List entries
func (t *Tristate) Set(s string) error {
s = strings.ToLower(s)
if s == "" || s == "nil" || s == "null" || s == "unset" {
t.Valid = false
return nil
}
value, err := strconv.ParseBool(s)
if err != nil {
return errors.Wrapf(err, "failed to parse Tristate %q", s)
}
t.Value = value
t.Valid = true
return nil
}
// Type of the value
func (Tristate) Type() string {
return "Tristate"
}
// Scan implements the fmt.Scanner interface
func (t *Tristate) Scan(s fmt.ScanState, ch rune) error {
token, err := s.Token(true, nil)
if err != nil {
return err
}
return t.Set(string(token))
}
// UnmarshalJSON parses it as a bool or nil for unset
func (t *Tristate) UnmarshalJSON(in []byte) error {
var b *bool
err := json.Unmarshal(in, &b)
if err != nil {
return err
}
if b != nil {
t.Valid = true
t.Value = *b
} else {
t.Valid = false
}
return nil
}

87
fs/tristate_test.go Normal file
View file

@ -0,0 +1,87 @@
package fs
import (
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Check it satisfies the interface
var _ flagger = (*Tristate)(nil)
func TestTristateString(t *testing.T) {
for _, test := range []struct {
in Tristate
want string
}{
{Tristate{}, "unset"},
{Tristate{Valid: false, Value: false}, "unset"},
{Tristate{Valid: false, Value: true}, "unset"},
{Tristate{Valid: true, Value: false}, "false"},
{Tristate{Valid: true, Value: true}, "true"},
} {
got := test.in.String()
assert.Equal(t, test.want, got)
}
}
func TestTristateSet(t *testing.T) {
for _, test := range []struct {
in string
want Tristate
err bool
}{
{"", Tristate{Valid: false, Value: false}, false},
{"nil", Tristate{Valid: false, Value: false}, false},
{"null", Tristate{Valid: false, Value: false}, false},
{"UNSET", Tristate{Valid: false, Value: false}, false},
{"true", Tristate{Valid: true, Value: true}, false},
{"1", Tristate{Valid: true, Value: true}, false},
{"false", Tristate{Valid: true, Value: false}, false},
{"0", Tristate{Valid: true, Value: false}, false},
{"potato", Tristate{Valid: false, Value: false}, true},
} {
var got Tristate
err := got.Set(test.in)
if test.err {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, test.want, got)
}
}
}
func TestTristateScan(t *testing.T) {
var v Tristate
n, err := fmt.Sscan(" true ", &v)
require.NoError(t, err)
assert.Equal(t, 1, n)
assert.Equal(t, Tristate{Valid: true, Value: true}, v)
}
func TestTristateUnmarshalJSON(t *testing.T) {
for _, test := range []struct {
in string
want Tristate
err bool
}{
{`null`, Tristate{}, false},
{`true`, Tristate{Valid: true, Value: true}, false},
{`false`, Tristate{Valid: true, Value: false}, false},
{`potato`, Tristate{}, true},
{``, Tristate{}, true},
} {
var got Tristate
err := json.Unmarshal([]byte(test.in), &got)
if test.err {
require.Error(t, err, test.in)
} else {
require.NoError(t, err, test.in)
}
assert.Equal(t, test.want, got, test.in)
}
}