From 3cfb1ccedbfff0bbc11b51f668ae194e37dd30d9 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 26 Jan 2024 20:00:03 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20Meta=20with=20TextMarshaler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/meta.go | 52 +++++++++++++++++++++++++++++++++++++++++++ models/meta_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 models/meta_test.go diff --git a/models/meta.go b/models/meta.go index 2640e7f..2b75fa9 100644 --- a/models/meta.go +++ b/models/meta.go @@ -1 +1,53 @@ package models + +import ( + "bytes" + "encoding/json" + "fmt" + "regexp" + "strconv" + "time" +) + +type Meta struct { + Key string + Value any +} + +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) + } + buff := &bytes.Buffer{} + buff.WriteRune('@') + buff.WriteString(m.Key) + buff.WriteRune(' ') + switch v := m.Value.(type) { + default: + return nil, fmt.Errorf("Unknown type %T", v) + case nil: + return []byte{}, nil + case string: + buff.WriteString(v) + case int: + buff.WriteString(strconv.Itoa(v)) + case float64: + buff.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) + case json.Number: + buff.WriteString(v.String()) + case json.RawMessage: + buff.Write(v) + case []byte: + buff.Write(v) + case byte: + buff.WriteByte(v) + case rune: + buff.WriteString(string(v)) + case bool: + buff.WriteString(strconv.FormatBool(v)) + case time.Time: + buff.WriteString(v.Format(time.RFC3339)) + } + + return buff.Bytes(), nil +} diff --git a/models/meta_test.go b/models/meta_test.go new file mode 100644 index 0000000..e9147ed --- /dev/null +++ b/models/meta_test.go @@ -0,0 +1,54 @@ +package models + +import ( + "encoding" + "encoding/json" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// Type assertions +var _ encoding.TextMarshaler = Meta{} + +func TestMeta(t *testing.T) { + when := time.Now() + tests := []struct { + name string + key string + value any + out string + err error + }{ + {"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")}, + } + + for _, tt := range tests { + t.Run(tt.name, getMetaTestRunner(tt.key, tt.value, tt.out, tt.err)) + } +} + +func getMetaTestRunner(key string, value any, out string, err error) 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) + } +}