mirror of
https://github.com/jimeh/go-render.git
synced 2026-02-19 11:26:39 +00:00
feat(text): expand supported value types
This commit is contained in:
37
text.go
37
text.go
@@ -5,19 +5,50 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Text is a renderer that writes the given value to the writer as is. It
|
||||||
|
// supports rendering the following types as plain text:
|
||||||
|
//
|
||||||
|
// - []byte
|
||||||
|
// - []rune
|
||||||
|
// - string
|
||||||
|
// - int, int8, int16, int32, int64
|
||||||
|
// - uint, uint8, uint16, uint32, uint64
|
||||||
|
// - float32, float64
|
||||||
|
// - bool
|
||||||
|
// - io.Reader
|
||||||
|
// - io.WriterTo
|
||||||
|
// - fmt.Stringer
|
||||||
|
// - error
|
||||||
|
//
|
||||||
|
// If the value is of any other type, a ErrCannotRender error will be returned.
|
||||||
type Text struct{}
|
type Text struct{}
|
||||||
|
|
||||||
var _ FormatRenderer = (*Text)(nil)
|
var _ FormatRenderer = (*Text)(nil)
|
||||||
|
|
||||||
|
// Render writes the given value to the writer as text.
|
||||||
func (t *Text) Render(w io.Writer, v any) error {
|
func (t *Text) Render(w io.Writer, v any) error {
|
||||||
var err error
|
var err error
|
||||||
switch x := v.(type) {
|
switch x := v.(type) {
|
||||||
case fmt.Stringer:
|
case []byte:
|
||||||
_, err = w.Write([]byte(x.String()))
|
_, err = w.Write(x)
|
||||||
|
case []rune:
|
||||||
|
_, err = w.Write([]byte(string(x)))
|
||||||
|
case string:
|
||||||
|
_, err = w.Write([]byte(x))
|
||||||
|
case int, int8, int16, int32, int64,
|
||||||
|
uint, uint8, uint16, uint32, uint64,
|
||||||
|
float32, float64, bool:
|
||||||
|
_, err = fmt.Fprintf(w, "%v", x)
|
||||||
|
case io.Reader:
|
||||||
|
_, err = io.Copy(w, x)
|
||||||
case io.WriterTo:
|
case io.WriterTo:
|
||||||
_, err = x.WriteTo(w)
|
_, err = x.WriteTo(w)
|
||||||
|
case fmt.Stringer:
|
||||||
|
_, err = w.Write([]byte(x.String()))
|
||||||
|
case error:
|
||||||
|
_, err = w.Write([]byte(x.Error()))
|
||||||
default:
|
default:
|
||||||
return ErrCannotRender
|
return fmt.Errorf("%w: %T", ErrCannotRender, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
134
text_test.go
134
text_test.go
@@ -37,6 +37,58 @@ func (m *mockWriterTo) WriteTo(w io.Writer) (int64, error) {
|
|||||||
return int64(n), err
|
return int64(n), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockReader struct {
|
||||||
|
value string
|
||||||
|
cursor int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.Reader = (*mockReader)(nil)
|
||||||
|
|
||||||
|
func (m *mockReader) Read(p []byte) (n int, err error) {
|
||||||
|
if m.err != nil {
|
||||||
|
return 0, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.value) == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n = copy(p, m.value[m.cursor:])
|
||||||
|
m.cursor += n
|
||||||
|
|
||||||
|
if m.cursor >= len(m.value) {
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_mockReader_Read(t *testing.T) {
|
||||||
|
mr := &mockReader{value: "test string"}
|
||||||
|
|
||||||
|
b1 := make([]byte, 5)
|
||||||
|
n1, err := mr.Read(b1)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 5, n1)
|
||||||
|
assert.Equal(t, "test ", string(b1))
|
||||||
|
|
||||||
|
b2 := make([]byte, 5)
|
||||||
|
n2, err := mr.Read(b2)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 5, n2)
|
||||||
|
assert.Equal(t, "strin", string(b2))
|
||||||
|
|
||||||
|
b3 := make([]byte, 5)
|
||||||
|
n3, err := mr.Read(b3)
|
||||||
|
|
||||||
|
assert.Equal(t, io.EOF, err)
|
||||||
|
assert.Equal(t, 1, n3)
|
||||||
|
assert.Equal(t, []byte{byte('g'), 0, 0, 0, 0}, b3)
|
||||||
|
}
|
||||||
|
|
||||||
func TestText_Render(t *testing.T) {
|
func TestText_Render(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -46,6 +98,51 @@ func TestText_Render(t *testing.T) {
|
|||||||
wantErr string
|
wantErr string
|
||||||
wantErrIs []error
|
wantErrIs []error
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
value: nil,
|
||||||
|
wantErr: "render: cannot render: <nil>",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "byte slice",
|
||||||
|
value: []byte("test byte slice"),
|
||||||
|
want: "test byte slice",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil byte slice",
|
||||||
|
value: []byte(nil),
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty byte slice",
|
||||||
|
value: []byte{},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rune slice",
|
||||||
|
value: []rune{'r', 'u', 'n', 'e', 's', '!', ' ', 'y', 'e', 's'},
|
||||||
|
want: "runes! yes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string",
|
||||||
|
value: "test string",
|
||||||
|
want: "test string",
|
||||||
|
},
|
||||||
|
{name: "int", value: int(42), want: "42"},
|
||||||
|
{name: "int8", value: int8(43), want: "43"},
|
||||||
|
{name: "int16", value: int16(44), want: "44"},
|
||||||
|
{name: "int32", value: int32(45), want: "45"},
|
||||||
|
{name: "int64", value: int64(46), want: "46"},
|
||||||
|
{name: "uint", value: uint(47), want: "47"},
|
||||||
|
{name: "uint8", value: uint8(48), want: "48"},
|
||||||
|
{name: "uint16", value: uint16(49), want: "49"},
|
||||||
|
{name: "uint32", value: uint32(50), want: "50"},
|
||||||
|
{name: "uint64", value: uint64(51), want: "51"},
|
||||||
|
{name: "float32", value: float32(3.14), want: "3.14"},
|
||||||
|
{name: "float64", value: float64(3.14159), want: "3.14159"},
|
||||||
|
{name: "bool true", value: true, want: "true"},
|
||||||
|
{name: "bool false", value: false, want: "false"},
|
||||||
{
|
{
|
||||||
name: "implements fmt.Stringer",
|
name: "implements fmt.Stringer",
|
||||||
value: &mockStringer{value: "test string"},
|
value: &mockStringer{value: "test string"},
|
||||||
@@ -64,13 +161,7 @@ func TestText_Render(t *testing.T) {
|
|||||||
want: "test string",
|
want: "test string",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "does not implement fmt.Stringer or io.WriterTo",
|
name: "io.WriterTo error",
|
||||||
value: struct{}{},
|
|
||||||
wantErr: "render: cannot render",
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "error writing to writer with io.WriterTo",
|
|
||||||
value: &mockWriterTo{
|
value: &mockWriterTo{
|
||||||
value: "test string",
|
value: "test string",
|
||||||
err: errors.New("WriteTo error!!1"),
|
err: errors.New("WriteTo error!!1"),
|
||||||
@@ -78,6 +169,31 @@ func TestText_Render(t *testing.T) {
|
|||||||
wantErr: "render: failed: WriteTo error!!1",
|
wantErr: "render: failed: WriteTo error!!1",
|
||||||
wantErrIs: []error{render.Err, render.ErrFailed},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "implements io.Reader",
|
||||||
|
value: &mockReader{value: "reader string"},
|
||||||
|
want: "reader string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "io.Reader error",
|
||||||
|
value: &mockReader{
|
||||||
|
value: "reader string",
|
||||||
|
err: errors.New("Read error!!1"),
|
||||||
|
},
|
||||||
|
wantErr: "render: failed: Read error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error",
|
||||||
|
value: errors.New("this is an error"),
|
||||||
|
want: "this is an error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "does not implement any supported type/interface",
|
||||||
|
value: struct{}{},
|
||||||
|
wantErr: "render: cannot render: struct {}",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -101,3 +217,7 @@ func TestText_Render(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ptr[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user