✨ Meta implements TextUnmarshaler
This commit is contained in:
parent
3cfb1ccedb
commit
875ca7e33d
2 changed files with 120 additions and 26 deletions
|
|
@ -3,9 +3,11 @@ package models
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -16,7 +18,7 @@ type Meta struct {
|
|||
|
||||
func (m Meta) MarshalText() ([]byte, error) {
|
||||
if regexp.MustCompile(`\s`).MatchString(m.Key) {
|
||||
return []byte{}, fmt.Errorf("whitespace is now allowed in key: %s", m.Key)
|
||||
return []byte{}, fmt.Errorf("whitespace is not allowed in key: %s", m.Key)
|
||||
}
|
||||
buff := &bytes.Buffer{}
|
||||
buff.WriteRune('@')
|
||||
|
|
@ -51,3 +53,51 @@ func (m Meta) MarshalText() ([]byte, error) {
|
|||
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
func (m *Meta) UnmarshalText(in []byte) error {
|
||||
if len(in) == 0 {
|
||||
return errors.New("Unable to Unmarshal empty string")
|
||||
}
|
||||
re := regexp.MustCompile("(?s)^@([^ ]+) (.*)( @end)?$")
|
||||
match := re.FindSubmatch(in)
|
||||
if len(match) == 0 {
|
||||
return fmt.Errorf("Failed to match %s", in)
|
||||
}
|
||||
m.Key = string(match[1])
|
||||
return m.processMeta(match[2])
|
||||
}
|
||||
|
||||
func (m *Meta) processMeta(in []byte) error {
|
||||
if len(in) == 0 {
|
||||
return errors.New("No value found")
|
||||
}
|
||||
s := strings.TrimSpace(string(in))
|
||||
if len(s) == 0 {
|
||||
return errors.New("No value found")
|
||||
}
|
||||
yesno := regexp.MustCompile("^(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$")
|
||||
yes := regexp.MustCompile("^(y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON)$")
|
||||
null := regexp.MustCompile("^(~|null|Null|NULL|none|None|NONE|nil|Nil|NIL)$")
|
||||
var j json.RawMessage
|
||||
if null.MatchString(s) {
|
||||
m.Value = nil
|
||||
} else if yesno.MatchString(s) {
|
||||
if yes.MatchString(s) {
|
||||
m.Value = true
|
||||
} else {
|
||||
m.Value = false
|
||||
}
|
||||
} else if i, err := strconv.Atoi(s); err == nil {
|
||||
m.Value = i
|
||||
} else if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
m.Value = f
|
||||
} else if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||
m.Value = t
|
||||
} else if err := json.Unmarshal(in, &j); err == nil {
|
||||
m.Value = j
|
||||
} else {
|
||||
m.Value = s
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,43 +12,87 @@ import (
|
|||
|
||||
// Type assertions
|
||||
var _ encoding.TextMarshaler = Meta{}
|
||||
var _ encoding.TextUnmarshaler = new(Meta)
|
||||
|
||||
var skipMarshalTest = errors.New("skip marshal")
|
||||
|
||||
func TestMeta(t *testing.T) {
|
||||
when := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
value any
|
||||
out string
|
||||
err error
|
||||
name string
|
||||
key string
|
||||
value any
|
||||
out string
|
||||
err error
|
||||
newVal any
|
||||
}{
|
||||
{"int", "num", 42, "@num 42", nil},
|
||||
{"float", "num", 42.13, "@num 42.13", nil},
|
||||
{"string", "word", "hello", "@word hello", nil},
|
||||
{"json number", "num", json.Number("42.13"), "@num 42.13", nil},
|
||||
{"true", "b", true, "@b true", nil},
|
||||
{"false", "b", false, "@b false", nil},
|
||||
{"nil", "n", nil, "", nil},
|
||||
{"time", "when", when, "@when " + when.Format(time.RFC3339), nil},
|
||||
{"rune", "char", '@', "@char @", nil},
|
||||
{"bytes", "byteme", []byte("yo"), "@byteme yo", nil},
|
||||
{"byte", "byteme", byte(67), "@byteme C", nil},
|
||||
{"json-obj", "obj", json.RawMessage(`{"foo":"bar","baz":"quux"}`), `@obj {"foo":"bar","baz":"quux"}`, nil},
|
||||
{"json-arr", "arr", json.RawMessage(`["foo",42,"bar", null,"quux", true]`), `@arr ["foo",42,"bar", null,"quux", true]`, nil},
|
||||
{"chan", "nope", make(chan bool), "", errors.New("Unknown type chan bool")},
|
||||
{"whitespace", "no space", "hi", "", errors.New("whitespace is now allowed in key: no space")},
|
||||
{"int", "num", 42, "@num 42", nil, 42},
|
||||
{"float", "num", 42.13, "@num 42.13", nil, 42.13},
|
||||
{"string", "word", "hello", "@word hello", nil, "hello"},
|
||||
{"json number", "num", json.Number("42.13"), "@num 42.13", nil, 42.13},
|
||||
{"true", "b", true, "@b true", nil, true},
|
||||
{"false", "b", false, "@b false", nil, false},
|
||||
{"nil", "n", nil, "", nil, errors.New("Unable to Unmarshal empty string")},
|
||||
{"time", "when", when, "@when " + when.Format(time.RFC3339), nil, when},
|
||||
{"rune", "char", '@', "@char @", nil, "@"},
|
||||
{"bytes", "byteme", []byte("yo"), "@byteme yo", nil, "yo"},
|
||||
{"byte", "byteme", byte(67), "@byteme C", nil, "C"},
|
||||
{"json-obj", "obj", json.RawMessage(`{"foo":"bar","baz":"quux"}`), `@obj {"foo":"bar","baz":"quux"}`, nil, json.RawMessage(`{"foo":"bar","baz":"quux"}`)},
|
||||
{"json-arr", "arr", json.RawMessage(`["foo",42,"bar", null,"quux", true]`), `@arr ["foo",42,"bar", null,"quux", true]`, nil, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)},
|
||||
{"chan", "nope", make(chan bool), "", errors.New("Unknown type chan bool"), ""},
|
||||
{"whitespace-key", "no space", "hi", "", errors.New("whitespace is not allowed in key: no space"), ""},
|
||||
{"empty-mar", "nope", skipMarshalTest, "", nil, errors.New("Unable to Unmarshal empty string")},
|
||||
{"no-key-mar", "nope", skipMarshalTest, "nope", nil, errors.New("Failed to match nope")},
|
||||
{"no-value-mar", "nope", skipMarshalTest, "@nope ", nil, errors.New("No value found")},
|
||||
{"space-value-mar", "nope", skipMarshalTest, "@nope ", nil, errors.New("No value found")},
|
||||
{"space-value-mar", "nope", skipMarshalTest, "@nope \n ", nil, errors.New("No value found")},
|
||||
{"null-value-mar", "nope", skipMarshalTest, "@nope null", nil, nil},
|
||||
{"tilda-value-mar", "nope", skipMarshalTest, "@nope ~", nil, nil},
|
||||
{"none-value-mar", "nope", skipMarshalTest, "@nope none", nil, nil},
|
||||
{"nil-value-mar", "nope", skipMarshalTest, "@nope nil", nil, nil},
|
||||
{"yes-value-mar", "nope", skipMarshalTest, "@nope yes", nil, true},
|
||||
{"on-value-mar", "nope", skipMarshalTest, "@nope on", nil, true},
|
||||
{"no-value-mar", "nope", skipMarshalTest, "@nope no", nil, false},
|
||||
{"off-value-mar", "nope", skipMarshalTest, "@nope off", nil, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, getMetaTestRunner(tt.key, tt.value, tt.out, tt.err))
|
||||
t.Run(tt.name, getMetaTestRunner(tt.key, tt.value, tt.out, tt.err, tt.newVal))
|
||||
}
|
||||
}
|
||||
|
||||
func getMetaTestRunner(key string, value any, out string, err error) func(*testing.T) {
|
||||
func getMetaTestRunner(key string, value any, out string, err error, newVal any) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
st := Meta{key, value}
|
||||
o, e := st.MarshalText()
|
||||
assert.Equal(t, out, string(o))
|
||||
assert.Equal(t, err, e)
|
||||
n := &Meta{}
|
||||
var e error
|
||||
|
||||
if valE, ok := value.(error); !ok || !errors.Is(valE, skipMarshalTest) {
|
||||
var o []byte
|
||||
o, e = st.MarshalText()
|
||||
assert.Equal(t, out, string(o))
|
||||
assert.Equal(t, err, e)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
e = n.UnmarshalText(o)
|
||||
} else {
|
||||
e = n.UnmarshalText([]byte(out))
|
||||
}
|
||||
if newE, ok := newVal.(error); ok {
|
||||
assert.Equal(t, newE, e)
|
||||
} else {
|
||||
assert.Equal(t, key, n.Key)
|
||||
if ti, ok := newVal.(time.Time); ok {
|
||||
valT, ok := n.Value.(time.Time)
|
||||
if assert.True(t, ok) {
|
||||
assert.WithinRange(t, valT, ti.Add(-time.Second), ti.Add(time.Second))
|
||||
}
|
||||
|
||||
} else {
|
||||
assert.Equal(t, newVal, n.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue