Commit b4ed6efd authored by Steven Allen's avatar Steven Allen

feat: add flag and priority types

These let us zero-encode "default" to "null" (and omit it with "omitempty") so
we don't have to hard code the default in the config.
parent 72534c1f
package config package config
import ( import (
"encoding"
"encoding/json" "encoding/json"
"fmt"
"time" "time"
) )
...@@ -41,6 +43,128 @@ func (o Strings) MarshalJSON() ([]byte, error) { ...@@ -41,6 +43,128 @@ func (o Strings) MarshalJSON() ([]byte, error) {
var _ json.Unmarshaler = (*Strings)(nil) var _ json.Unmarshaler = (*Strings)(nil)
var _ json.Marshaler = (*Strings)(nil) var _ json.Marshaler = (*Strings)(nil)
// Flag represents a ternary value: false (-1), default (0), or true (+1).
//
// When encoded in json, False is "false", Default is "null" (or empty), and True
// is "true".
type Flag int8
const (
False Flag = -1
Default Flag = 0
True Flag = 1
)
func (f Flag) MarshalJSON() ([]byte, error) {
switch f {
case Default:
return json.Marshal(nil)
case True:
return json.Marshal(true)
case False:
return json.Marshal(false)
default:
return nil, fmt.Errorf("invalid flag value: %d", f)
}
}
func (f *Flag) UnmarshalJSON(input []byte) error {
switch string(input) {
case "null", "undefined":
*f = Default
case "false":
*f = False
case "true":
*f = True
default:
return fmt.Errorf("failed to unmarshal %q into a flag: must be null/undefined, true, or false", string(input))
}
return nil
}
func (f Flag) String() string {
switch f {
case Default:
return "default"
case True:
return "true"
case False:
return "false"
default:
return fmt.Sprintf("<invalid flag value %d>", f)
}
}
var _ json.Unmarshaler = (*Flag)(nil)
var _ json.Marshaler = (*Flag)(nil)
// Priority represents a value with a priority where 0 means "default" and -11
// means "disabled".
//
// When encoded in json, Default is encoded as "null" and Disabled is encoded as
// "false".
type Priority int64
const (
DefaultPriority Priority = 0
Disabled Priority = -1
)
func (p Priority) MarshalJSON() ([]byte, error) {
// > 0 == Priority
if p > 0 {
return json.Marshal(int64(p))
}
// <= 0 == special
switch p {
case DefaultPriority:
return json.Marshal(nil)
case Disabled:
return json.Marshal(false)
default:
return nil, fmt.Errorf("invalid priority value: %d", p)
}
}
func (p *Priority) UnmarshalJSON(input []byte) error {
switch string(input) {
case "null", "undefined":
*p = DefaultPriority
case "false":
*p = Disabled
case "true":
return fmt.Errorf("'true' is not a valid priority")
default:
var priority int64
err := json.Unmarshal(input, &priority)
if err != nil {
return err
}
if priority <= 0 {
return fmt.Errorf("priority must be positive: %d <= 0", priority)
}
*p = Priority(priority)
}
return nil
}
func (p Priority) String() string {
if p > 0 {
return fmt.Sprintf("%d", p)
}
switch p {
case DefaultPriority:
return "default"
case Disabled:
return "false"
default:
return fmt.Sprintf("<invalid priority %d>", p)
}
}
var _ json.Unmarshaler = (*Flag)(nil)
var _ json.Marshaler = (*Flag)(nil)
// Duration wraps time.Duration to provide json serialization and deserialization. // Duration wraps time.Duration to provide json serialization and deserialization.
// //
// NOTE: the zero value encodes to an empty string. // NOTE: the zero value encodes to an empty string.
...@@ -59,3 +183,6 @@ func (d Duration) MarshalText() ([]byte, error) { ...@@ -59,3 +183,6 @@ func (d Duration) MarshalText() ([]byte, error) {
func (d Duration) String() string { func (d Duration) String() string {
return time.Duration(d).String() return time.Duration(d).String()
} }
var _ encoding.TextUnmarshaler = (*Duration)(nil)
var _ encoding.TextMarshaler = (*Duration)(nil)
...@@ -83,3 +83,102 @@ func TestFunkyStrings(t *testing.T) { ...@@ -83,3 +83,102 @@ func TestFunkyStrings(t *testing.T) {
t.Fatalf("unexpected result: %v", s) t.Fatalf("unexpected result: %v", s)
} }
} }
func TestFlag(t *testing.T) {
// make sure we have the right zero value.
var defaultFlag Flag
if defaultFlag != Default {
t.Errorf("expected default flag to be %q, got %q", Default, defaultFlag)
}
for jsonStr, goValue := range map[string]Flag{
"null": Default,
"true": True,
"false": False,
} {
var d Flag
err := json.Unmarshal([]byte(jsonStr), &d)
if err != nil {
t.Fatal(err)
}
if d != goValue {
t.Fatalf("expected %s, got %s", goValue, d)
}
// Reverse
out, err := json.Marshal(goValue)
if err != nil {
t.Fatal(err)
}
if string(out) != jsonStr {
t.Fatalf("expected %s, got %s", jsonStr, string(out))
}
}
type Foo struct {
F Flag `json:",omitempty"`
}
out, err := json.Marshal(new(Foo))
if err != nil {
t.Fatal(err)
}
expected := "{}"
if string(out) != expected {
t.Fatal("expected omitempty to omit the flag")
}
}
func TestPriority(t *testing.T) {
// make sure we have the right zero value.
var defaultPriority Priority
if defaultPriority != DefaultPriority {
t.Errorf("expected default priority to be %q, got %q", DefaultPriority, defaultPriority)
}
for jsonStr, goValue := range map[string]Priority{
"null": DefaultPriority,
"false": Disabled,
"1": 1,
"2": 2,
"100": 100,
} {
var d Priority
err := json.Unmarshal([]byte(jsonStr), &d)
if err != nil {
t.Fatal(err)
}
if d != goValue {
t.Fatalf("expected %s, got %s", goValue, d)
}
// Reverse
out, err := json.Marshal(goValue)
if err != nil {
t.Fatal(err)
}
if string(out) != jsonStr {
t.Fatalf("expected %s, got %s", jsonStr, string(out))
}
}
type Foo struct {
P Priority `json:",omitempty"`
}
out, err := json.Marshal(new(Foo))
if err != nil {
t.Fatal(err)
}
expected := "{}"
if string(out) != expected {
t.Fatal("expected omitempty to omit the flag")
}
for _, invalid := range []string{
"0", "-1", "-2", "1.1", "0.0",
} {
var p Priority
err := json.Unmarshal([]byte(invalid), &p)
if err == nil {
t.Errorf("expected to fail to decode %s as a priority", invalid)
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment