mirror of
https://github.com/jimeh/go-render.git
synced 2026-02-19 03:16:39 +00:00
refactor!: sizeable changes across the board
This commit is contained in:
@@ -10,7 +10,7 @@ import (
|
|||||||
// interface.
|
// interface.
|
||||||
type Binary struct{}
|
type Binary struct{}
|
||||||
|
|
||||||
var _ Renderer = (*Binary)(nil)
|
var _ FormatRenderer = (*Binary)(nil)
|
||||||
|
|
||||||
// Render writes result of calling MarshalBinary() on v. If v does not implment
|
// Render writes result of calling MarshalBinary() on v. If v does not implment
|
||||||
// encoding.BinaryMarshaler the ErrCannotRander error will be returned.
|
// encoding.BinaryMarshaler the ErrCannotRander error will be returned.
|
||||||
@@ -22,12 +22,12 @@ func (bm *Binary) Render(w io.Writer, v any) error {
|
|||||||
|
|
||||||
b, err := x.MarshalBinary()
|
b, err := x.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %w", Err, err)
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Write(b)
|
_, err = w.Write(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %w", Err, err)
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -45,15 +45,15 @@ func TestBinary_Render(t *testing.T) {
|
|||||||
data: []byte("test string"),
|
data: []byte("test string"),
|
||||||
err: errors.New("marshal error!!1"),
|
err: errors.New("marshal error!!1"),
|
||||||
},
|
},
|
||||||
wantErr: "render: marshal error!!1",
|
wantErr: "render: failed: marshal error!!1",
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error writing to writer",
|
name: "error writing to writer",
|
||||||
writeErr: errors.New("write error!!1"),
|
writeErr: errors.New("write error!!1"),
|
||||||
value: &mockBinaryMarshaler{data: []byte("test string")},
|
value: &mockBinaryMarshaler{data: []byte("test string")},
|
||||||
wantErr: "render: write error!!1",
|
wantErr: "render: failed: write error!!1",
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
32
format.go
32
format.go
@@ -1,32 +0,0 @@
|
|||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrUnsupportedFormat = fmt.Errorf("%w: unsupported format", Err)
|
|
||||||
|
|
||||||
// FormatRenderer is a renderer that delegates rendering to another renderer
|
|
||||||
// based on a format value.
|
|
||||||
type FormatRenderer struct {
|
|
||||||
// Renderers is a map of format names to renderers. When Render is called,
|
|
||||||
// the format is used to look up the renderer to use.
|
|
||||||
Renderers map[string]Renderer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render renders a value to an io.Writer using the specified format. If the
|
|
||||||
// format is not supported, ErrUnsupportedFormat is returned.
|
|
||||||
//
|
|
||||||
// If the format is supported, but the value cannot be rendered to the format,
|
|
||||||
// the error returned by the renderer is returned. In most cases this will be
|
|
||||||
// ErrCannotRender, but it could be a different error if the renderer returns
|
|
||||||
// one.
|
|
||||||
func (r *FormatRenderer) Render(w io.Writer, format string, v any) error {
|
|
||||||
renderer, ok := r.Renderers[format]
|
|
||||||
if ok {
|
|
||||||
return renderer.Render(w, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrUnsupportedFormat
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package render_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/jimeh/go-render"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormatRenderer_Render(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
renderers map[string]render.Renderer
|
|
||||||
format string
|
|
||||||
value interface{}
|
|
||||||
want string
|
|
||||||
wantErr string
|
|
||||||
wantErrIs []error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "existing renderer",
|
|
||||||
renderers: map[string]render.Renderer{
|
|
||||||
"mock": &mockRenderer{output: "mock output"},
|
|
||||||
},
|
|
||||||
format: "mock",
|
|
||||||
value: struct{}{},
|
|
||||||
want: "mock output",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "existing renderer returns error",
|
|
||||||
renderers: map[string]render.Renderer{
|
|
||||||
"other": &mockRenderer{
|
|
||||||
output: "mock output",
|
|
||||||
err: errors.New("mock error"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
format: "other",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErr: "mock error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-existing renderer",
|
|
||||||
renderers: map[string]render.Renderer{},
|
|
||||||
format: "unknown",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
fr := &render.FormatRenderer{
|
|
||||||
Renderers: tt.renderers,
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
err := fr.Render(&buf, tt.format, tt.value)
|
|
||||||
got := buf.String()
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
assert.EqualError(t, err, tt.wantErr)
|
|
||||||
}
|
|
||||||
for _, e := range tt.wantErrIs {
|
|
||||||
assert.ErrorIs(t, err, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.want, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4
json.go
4
json.go
@@ -21,7 +21,7 @@ type JSON struct {
|
|||||||
Indent string
|
Indent string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Renderer = (*JSON)(nil)
|
var _ FormatRenderer = (*JSON)(nil)
|
||||||
|
|
||||||
// Render marshals the given value to JSON.
|
// Render marshals the given value to JSON.
|
||||||
func (j *JSON) Render(w io.Writer, v any) error {
|
func (j *JSON) Render(w io.Writer, v any) error {
|
||||||
@@ -38,7 +38,7 @@ func (j *JSON) Render(w io.Writer, v any) error {
|
|||||||
|
|
||||||
err := enc.Encode(v)
|
err := enc.Encode(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %w", Err, err)
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -69,14 +69,14 @@ func TestJSON_Render(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "error from json.Marshaler",
|
name: "error from json.Marshaler",
|
||||||
value: &mockJSONMarshaler{err: errors.New("marshal error!!1")},
|
value: &mockJSONMarshaler{err: errors.New("marshal error!!1")},
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid value",
|
name: "invalid value",
|
||||||
pretty: false,
|
pretty: false,
|
||||||
value: make(chan int),
|
value: make(chan int),
|
||||||
wantErr: "render: json: unsupported type: chan int",
|
wantErr: "render: failed: json: unsupported type: chan int",
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
4
multi.go
4
multi.go
@@ -7,10 +7,10 @@ import (
|
|||||||
|
|
||||||
// MultiRenderer is a renderer that tries multiple renderers until one succeeds.
|
// MultiRenderer is a renderer that tries multiple renderers until one succeeds.
|
||||||
type MultiRenderer struct {
|
type MultiRenderer struct {
|
||||||
Renderers []Renderer
|
Renderers []FormatRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Renderer = (*MultiRenderer)(nil)
|
var _ FormatRenderer = (*MultiRenderer)(nil)
|
||||||
|
|
||||||
// Render tries each renderer in order until one succeeds. If none succeed,
|
// Render tries each renderer in order until one succeeds. If none succeed,
|
||||||
// ErrCannotRender is returned. If a renderer returns an error that is not
|
// ErrCannotRender is returned. If a renderer returns an error that is not
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestMultiRenderer_Render(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
renderers []render.Renderer
|
renderers []render.FormatRenderer
|
||||||
value interface{}
|
value interface{}
|
||||||
want string
|
want string
|
||||||
wantErr string
|
wantErr string
|
||||||
@@ -24,7 +24,7 @@ func TestMultiRenderer_Render(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no renderer can render",
|
name: "no renderer can render",
|
||||||
renderers: []render.Renderer{
|
renderers: []render.FormatRenderer{
|
||||||
cannotRenderer,
|
cannotRenderer,
|
||||||
cannotRenderer,
|
cannotRenderer,
|
||||||
},
|
},
|
||||||
@@ -33,7 +33,7 @@ func TestMultiRenderer_Render(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one renderer can render",
|
name: "one renderer can render",
|
||||||
renderers: []render.Renderer{
|
renderers: []render.FormatRenderer{
|
||||||
cannotRenderer,
|
cannotRenderer,
|
||||||
successRenderer,
|
successRenderer,
|
||||||
cannotRenderer,
|
cannotRenderer,
|
||||||
@@ -43,7 +43,7 @@ func TestMultiRenderer_Render(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple renderers can render",
|
name: "multiple renderers can render",
|
||||||
renderers: []render.Renderer{
|
renderers: []render.FormatRenderer{
|
||||||
&mockRenderer{err: render.ErrCannotRender},
|
&mockRenderer{err: render.ErrCannotRender},
|
||||||
&mockRenderer{output: "first output"},
|
&mockRenderer{output: "first output"},
|
||||||
&mockRenderer{output: "second output"},
|
&mockRenderer{output: "second output"},
|
||||||
@@ -53,7 +53,7 @@ func TestMultiRenderer_Render(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "first renderer fails",
|
name: "first renderer fails",
|
||||||
renderers: []render.Renderer{
|
renderers: []render.FormatRenderer{
|
||||||
failRenderer,
|
failRenderer,
|
||||||
successRenderer,
|
successRenderer,
|
||||||
},
|
},
|
||||||
@@ -62,7 +62,7 @@ func TestMultiRenderer_Render(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fails after cannot render",
|
name: "fails after cannot render",
|
||||||
renderers: []render.Renderer{
|
renderers: []render.FormatRenderer{
|
||||||
cannotRenderer,
|
cannotRenderer,
|
||||||
failRenderer,
|
failRenderer,
|
||||||
successRenderer,
|
successRenderer,
|
||||||
@@ -72,7 +72,7 @@ func TestMultiRenderer_Render(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fails after success render",
|
name: "fails after success render",
|
||||||
renderers: []render.Renderer{
|
renderers: []render.FormatRenderer{
|
||||||
successRenderer,
|
successRenderer,
|
||||||
failRenderer,
|
failRenderer,
|
||||||
cannotRenderer,
|
cannotRenderer,
|
||||||
|
|||||||
135
render.go
135
render.go
@@ -1,3 +1,14 @@
|
|||||||
|
// Package render provides a simple and flexible way to render a value to a
|
||||||
|
// io.Writer using different formats based on a format string argument.
|
||||||
|
//
|
||||||
|
// It allows rendering a custom type which can be marshaled to JSON, YAML, XML,
|
||||||
|
// while also supporting plain text by implementing fmt.Stringer or io.WriterTo.
|
||||||
|
// Binary output is also supported by implementing the encoding.BinaryMarshaler
|
||||||
|
// interface.
|
||||||
|
//
|
||||||
|
// Originally intended to easily implement CLI tools which can output their data
|
||||||
|
// as plain text, as well as JSON/YAML with a simple switch of a format string.
|
||||||
|
// But it can just as easily render to any io.Writer.
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -6,24 +17,43 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Err = fmt.Errorf("render")
|
// Err is the base error for the package. All errors returned by this
|
||||||
|
// package are wrapped with this error.
|
||||||
|
Err = fmt.Errorf("render")
|
||||||
|
ErrFailed = fmt.Errorf("%w: failed", Err)
|
||||||
|
|
||||||
// ErrCannotRender is returned when a value cannot be rendered. This may be
|
// ErrCannotRender is returned when a value cannot be rendered. This may be
|
||||||
// due to the value not supporting the format, or the value itself not being
|
// due to the value not supporting the format, or the value itself not being
|
||||||
// renderable.
|
// renderable. Only Renderer implementations should return this error.
|
||||||
ErrCannotRender = fmt.Errorf("%w: cannot render", Err)
|
ErrCannotRender = fmt.Errorf("%w: cannot render", Err)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Renderer is the interface that that individual renderers must implement.
|
// FormatRenderer interface is for single format renderers, which can only
|
||||||
type Renderer interface {
|
// render a single format.
|
||||||
|
type FormatRenderer interface {
|
||||||
|
// Render writes v into w in the format that the FormatRenderer supports.
|
||||||
|
//
|
||||||
|
// If v does not implement a required interface, or otherwise cannot be
|
||||||
|
// rendered to the format in question, then a ErrCannotRender error must be
|
||||||
|
// returned. Any other errors should be returned as is.
|
||||||
Render(w io.Writer, v any) error
|
Render(w io.Writer, v any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// DefaultBinary is the default binary marshaler renderer. It
|
||||||
|
// renders values using the encoding.BinaryMarshaler interface.
|
||||||
|
DefaultBinary = &Binary{}
|
||||||
|
|
||||||
// DefaultJSON is the default JSON renderer. It renders values using the
|
// DefaultJSON is the default JSON renderer. It renders values using the
|
||||||
// encoding/json package, with pretty printing enabled.
|
// encoding/json package, with pretty printing enabled.
|
||||||
DefaultJSON = &JSON{Pretty: true}
|
DefaultJSON = &JSON{Pretty: true}
|
||||||
|
|
||||||
|
// DefaultText is the default text renderer, used by the package level
|
||||||
|
// Render function. It renders values using the DefaultStringer and
|
||||||
|
// DefaultWriterTo renderers. This means a value must implement either the
|
||||||
|
// fmt.Stringer or io.WriterTo interfaces to be rendered.
|
||||||
|
DefaultText = &Text{}
|
||||||
|
|
||||||
// DefaultXML is the default XML renderer. It renders values using the
|
// DefaultXML is the default XML renderer. It renders values using the
|
||||||
// encoding/xml package, with pretty printing enabled.
|
// encoding/xml package, with pretty printing enabled.
|
||||||
DefaultXML = &XML{Pretty: true}
|
DefaultXML = &XML{Pretty: true}
|
||||||
@@ -32,39 +62,10 @@ var (
|
|||||||
// gopkg.in/yaml.v3 package, with an indentation of 2 spaces.
|
// gopkg.in/yaml.v3 package, with an indentation of 2 spaces.
|
||||||
DefaultYAML = &YAML{Indent: 2}
|
DefaultYAML = &YAML{Indent: 2}
|
||||||
|
|
||||||
// DefaultWriterTo is the default writer to renderer. It renders values
|
// DefaultRenderer is used by the package level Render function. It supports
|
||||||
// using the io.WriterTo interface.
|
// the text", "json", and "yaml" formats. If you need to support another set
|
||||||
DefaultWriterTo = &WriterTo{}
|
// of formats, use the New function to create a custom FormatRenderer.
|
||||||
|
DefaultRenderer = MustNew("json", "text", "yaml")
|
||||||
// DefaultStringer is the default stringer renderer. It renders values
|
|
||||||
// using the fmt.Stringer interface.
|
|
||||||
DefaultStringer = &Stringer{}
|
|
||||||
|
|
||||||
// DefaultText is the default text renderer, used by the package level
|
|
||||||
// Render function. It renders values using the DefaultStringer and
|
|
||||||
// DefaultWriterTo renderers. This means a value must implement either the
|
|
||||||
// fmt.Stringer or io.WriterTo interfaces to be rendered.
|
|
||||||
DefaultText = &MultiRenderer{
|
|
||||||
Renderers: []Renderer{DefaultStringer, DefaultWriterTo},
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultBinary is the default binary marshaler renderer. It
|
|
||||||
// renders values using the encoding.BinaryMarshaler interface.
|
|
||||||
DefaultBinary = &Binary{}
|
|
||||||
|
|
||||||
// DefaultRenderer is the default renderer, used by the package level Render
|
|
||||||
// function.
|
|
||||||
DefaultRenderer = &FormatRenderer{map[string]Renderer{
|
|
||||||
"bin": DefaultBinary,
|
|
||||||
"binary": DefaultBinary,
|
|
||||||
"json": DefaultJSON,
|
|
||||||
"plain": DefaultText,
|
|
||||||
"text": DefaultText,
|
|
||||||
"txt": DefaultText,
|
|
||||||
"xml": DefaultXML,
|
|
||||||
"yaml": DefaultYAML,
|
|
||||||
"yml": DefaultYAML,
|
|
||||||
}}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Render renders the given value to the given writer using the given format.
|
// Render renders the given value to the given writer using the given format.
|
||||||
@@ -72,23 +73,63 @@ var (
|
|||||||
//
|
//
|
||||||
// By default it supports the following formats:
|
// By default it supports the following formats:
|
||||||
//
|
//
|
||||||
|
// - "text": Renders values using the fmt.Stringer and io.WriterTo interfaces.
|
||||||
// - "json": Renders values using the encoding/json package, with pretty
|
// - "json": Renders values using the encoding/json package, with pretty
|
||||||
// printing enabled.
|
// printing enabled.
|
||||||
// - "yaml": Renders values using the gopkg.in/yaml.v3 package, with an
|
// - "yaml": Renders values using the gopkg.in/yaml.v3 package, with an
|
||||||
// indentation of 2 spaces.
|
// indentation of 2 spaces.
|
||||||
// - "yml": Alias for "yaml".
|
|
||||||
// - "xml": Renders values using the encoding/xml package, with pretty
|
|
||||||
// printing enabled.
|
|
||||||
// - "text": Renders values using the fmt.Stringer and io.WriterTo interfaces.
|
|
||||||
// This means a value must implement either the fmt.Stringer or io.WriterTo
|
|
||||||
// interfaces to be rendered.
|
|
||||||
// - "txt": Alias for "text".
|
|
||||||
// - "plain": Alias for "text".
|
|
||||||
// - "binary": Renders values using the encoding.BinaryMarshaler interface.
|
|
||||||
// - "bin": Alias for "binary".
|
|
||||||
//
|
//
|
||||||
// If the format is not supported, a ErrUnsupportedFormat error will be
|
// If the format is not supported, a ErrUnsupportedFormat error will be
|
||||||
// returned.
|
// returned.
|
||||||
|
//
|
||||||
|
// If you need to support a custom set of formats, use the New function to
|
||||||
|
// create a new FormatRenderer with the formats you need. If you need new custom
|
||||||
|
// renderers, manually create a new FormatRenderer.
|
||||||
func Render(w io.Writer, format string, v any) error {
|
func Render(w io.Writer, format string, v any) error {
|
||||||
return DefaultRenderer.Render(w, format, v)
|
return DefaultRenderer.Render(w, format, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New creates a new *FormatRenderer with support for the given formats.
|
||||||
|
//
|
||||||
|
// Supported formats are:
|
||||||
|
//
|
||||||
|
// - "binary": Renders values using DefaultBinary.
|
||||||
|
// - "json": Renders values using DefaultJSON.
|
||||||
|
// - "text": Renders values using DefaultText.
|
||||||
|
// - "xml": Renders values using DefaultXML.
|
||||||
|
// - "yaml": Renders values using DefaultYAML.
|
||||||
|
//
|
||||||
|
// If an unsupported format is given, an ErrUnsupportedFormat error will be
|
||||||
|
// returned.
|
||||||
|
func New(formats ...string) (*Renderer, error) {
|
||||||
|
renderers := map[string]FormatRenderer{}
|
||||||
|
|
||||||
|
for _, format := range formats {
|
||||||
|
switch format {
|
||||||
|
case "binary":
|
||||||
|
renderers[format] = DefaultBinary
|
||||||
|
case "json":
|
||||||
|
renderers[format] = DefaultJSON
|
||||||
|
case "text":
|
||||||
|
renderers[format] = DefaultText
|
||||||
|
case "xml":
|
||||||
|
renderers[format] = DefaultXML
|
||||||
|
case "yaml":
|
||||||
|
renderers[format] = DefaultYAML
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrUnsupportedFormat, format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewFormatRenderer(renderers), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNew is like New, but panics if an error occurs.
|
||||||
|
func MustNew(formats ...string) *Renderer {
|
||||||
|
r, err := New(formats...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ func ExampleRender_json() {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
err := render.Render(buf, "json", data)
|
err := render.Render(buf, "json", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
fmt.Printf("err: %s\n", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
// panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println(buf.String())
|
fmt.Println(buf.String())
|
||||||
|
|
||||||
@@ -139,9 +142,13 @@ func ExampleRender_xml() {
|
|||||||
Tags: []string{"golang", "json", "yaml", "toml"},
|
Tags: []string{"golang", "json", "yaml", "toml"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new renderer that supports XML in addition to default JSON, YAML
|
||||||
|
// and Text.
|
||||||
|
r := render.MustNew("json", "text", "xml", "yaml")
|
||||||
|
|
||||||
// Render the object to XML.
|
// Render the object to XML.
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
err := render.Render(buf, "xml", data)
|
err := r.Render(buf, "xml", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
654
render_test.go
654
render_test.go
@@ -35,7 +35,7 @@ type mockRenderer struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ render.Renderer = (*mockRenderer)(nil)
|
var _ render.FormatRenderer = (*mockRenderer)(nil)
|
||||||
|
|
||||||
func (m *mockRenderer) Render(w io.Writer, _ any) error {
|
func (m *mockRenderer) Render(w io.Writer, _ any) error {
|
||||||
_, err := w.Write([]byte(m.output))
|
_, err := w.Write([]byte(m.output))
|
||||||
@@ -59,23 +59,8 @@ func TestDefaultYAML(t *testing.T) {
|
|||||||
assert.Equal(t, &render.YAML{Indent: 2}, render.DefaultYAML)
|
assert.Equal(t, &render.YAML{Indent: 2}, render.DefaultYAML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultWriterTo(t *testing.T) {
|
|
||||||
assert.Equal(t, &render.WriterTo{}, render.DefaultWriterTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultStringer(t *testing.T) {
|
|
||||||
assert.Equal(t, &render.Stringer{}, render.DefaultStringer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultText(t *testing.T) {
|
func TestDefaultText(t *testing.T) {
|
||||||
want := &render.MultiRenderer{
|
assert.Equal(t, &render.Text{}, render.DefaultText)
|
||||||
Renderers: []render.Renderer{
|
|
||||||
&render.Stringer{},
|
|
||||||
&render.WriterTo{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, want, render.DefaultText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultBinary(t *testing.T) {
|
func TestDefaultBinary(t *testing.T) {
|
||||||
@@ -83,408 +68,253 @@ func TestDefaultBinary(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultRenderer(t *testing.T) {
|
func TestDefaultRenderer(t *testing.T) {
|
||||||
want := &render.FormatRenderer{
|
want := &render.Renderer{
|
||||||
Renderers: map[string]render.Renderer{
|
Formats: map[string]render.FormatRenderer{
|
||||||
"bin": render.DefaultBinary,
|
"json": render.DefaultJSON,
|
||||||
"binary": render.DefaultBinary,
|
"text": render.DefaultText,
|
||||||
"json": render.DefaultJSON,
|
"yaml": render.DefaultYAML,
|
||||||
"plain": render.DefaultText,
|
|
||||||
"text": render.DefaultText,
|
|
||||||
"txt": render.DefaultText,
|
|
||||||
"xml": render.DefaultXML,
|
|
||||||
"yaml": render.DefaultYAML,
|
|
||||||
"yml": render.DefaultYAML,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, want, render.DefaultRenderer)
|
assert.Equal(t, want, render.DefaultRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type renderFormatTestCase struct {
|
||||||
|
name string
|
||||||
|
writeErr error
|
||||||
|
format string
|
||||||
|
value any
|
||||||
|
want string
|
||||||
|
wantErr string
|
||||||
|
wantErrIs []error
|
||||||
|
wantPanic string
|
||||||
|
}
|
||||||
|
|
||||||
|
// "binary" format.
|
||||||
|
var binaryFormattestCases = []renderFormatTestCase{
|
||||||
|
{
|
||||||
|
name: "binary format with binary marshaler",
|
||||||
|
format: "binary",
|
||||||
|
value: &mockBinaryMarshaler{data: []byte("test string")},
|
||||||
|
want: "test string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binary format without binary marshaler",
|
||||||
|
format: "binary",
|
||||||
|
value: struct{}{},
|
||||||
|
wantErr: "render: unsupported format: binary",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binary format with error marshaling",
|
||||||
|
format: "binary",
|
||||||
|
value: &mockBinaryMarshaler{
|
||||||
|
data: []byte("test string"),
|
||||||
|
err: errors.New("marshal error!!1"),
|
||||||
|
},
|
||||||
|
wantErr: "render: failed: marshal error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binary format with error writing to writer",
|
||||||
|
format: "binary",
|
||||||
|
writeErr: errors.New("write error!!1"),
|
||||||
|
value: &mockBinaryMarshaler{data: []byte("test string")},
|
||||||
|
wantErr: "render: failed: write error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binary format with invalid type",
|
||||||
|
format: "binary",
|
||||||
|
value: make(chan int),
|
||||||
|
wantErr: "render: unsupported format: binary",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// "json" format.
|
||||||
|
var jsonFormatTestCases = []renderFormatTestCase{
|
||||||
|
{
|
||||||
|
name: "json format",
|
||||||
|
format: "json",
|
||||||
|
value: map[string]int{"age": 30},
|
||||||
|
want: "{\n \"age\": 30\n}\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json format with json marshaler",
|
||||||
|
format: "json",
|
||||||
|
value: &mockJSONMarshaler{data: []byte(`{"age":30}`)},
|
||||||
|
want: "{\n \"age\": 30\n}\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json format with error from json marshaler",
|
||||||
|
format: "json",
|
||||||
|
value: &mockJSONMarshaler{err: errors.New("marshal error!!1")},
|
||||||
|
wantErrIs: []error{render.Err},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json format with error writing to writer",
|
||||||
|
format: "json",
|
||||||
|
writeErr: errors.New("write error!!1"),
|
||||||
|
value: map[string]int{"age": 30},
|
||||||
|
wantErr: "render: failed: write error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json format with invalid type",
|
||||||
|
format: "json",
|
||||||
|
value: make(chan int),
|
||||||
|
wantErr: "render: failed: json: unsupported type: chan int",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// "text" format.
|
||||||
|
var textFormatTestCases = []renderFormatTestCase{
|
||||||
|
{
|
||||||
|
name: "text format with fmt.Stringer",
|
||||||
|
format: "text",
|
||||||
|
value: &mockStringer{value: "test string"},
|
||||||
|
want: "test string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text format with io.WriterTo",
|
||||||
|
format: "text",
|
||||||
|
value: &mockWriterTo{value: "test string"},
|
||||||
|
want: "test string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text format without fmt.Stringer or io.WriterTo",
|
||||||
|
format: "text",
|
||||||
|
value: struct{}{},
|
||||||
|
wantErr: "render: unsupported format: text",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text format with error writing to writer",
|
||||||
|
format: "text",
|
||||||
|
writeErr: errors.New("write error!!1"),
|
||||||
|
value: &mockStringer{value: "test string"},
|
||||||
|
wantErr: "render: failed: write error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text format with error from io.WriterTo",
|
||||||
|
format: "text",
|
||||||
|
value: &mockWriterTo{
|
||||||
|
value: "test string",
|
||||||
|
err: errors.New("WriteTo error!!1"),
|
||||||
|
},
|
||||||
|
wantErr: "render: failed: WriteTo error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text format with invalid type",
|
||||||
|
format: "text",
|
||||||
|
value: make(chan int),
|
||||||
|
wantErr: "render: unsupported format: text",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// "xml" format.
|
||||||
|
var xmlFormatTestCases = []renderFormatTestCase{
|
||||||
|
{
|
||||||
|
name: "xml format",
|
||||||
|
format: "xml",
|
||||||
|
value: struct {
|
||||||
|
XMLName xml.Name `xml:"user"`
|
||||||
|
Age int `xml:"age"`
|
||||||
|
}{Age: 30},
|
||||||
|
want: "<user>\n <age>30</age>\n</user>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "xml format with xml.Marshaler",
|
||||||
|
format: "xml",
|
||||||
|
value: &mockXMLMarshaler{elm: "test string"},
|
||||||
|
want: "<mockXMLMarshaler>test string</mockXMLMarshaler>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "xml format with error from xml.Marshaler",
|
||||||
|
format: "xml",
|
||||||
|
value: &mockXMLMarshaler{err: errors.New("marshal error!!1")},
|
||||||
|
wantErr: "render: failed: marshal error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "xml format with error writing to writer",
|
||||||
|
format: "xml",
|
||||||
|
writeErr: errors.New("write error!!1"),
|
||||||
|
value: struct {
|
||||||
|
XMLName xml.Name `xml:"user"`
|
||||||
|
Age int `xml:"age"`
|
||||||
|
}{Age: 30},
|
||||||
|
wantErr: "render: failed: write error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "xml format with invalid value",
|
||||||
|
format: "xml",
|
||||||
|
value: make(chan int),
|
||||||
|
wantErr: "render: failed: xml: unsupported type: chan int",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// "yaml" format.
|
||||||
|
var yamlFormatTestCases = []renderFormatTestCase{
|
||||||
|
{
|
||||||
|
name: "yaml format",
|
||||||
|
format: "yaml",
|
||||||
|
value: map[string]int{"age": 30},
|
||||||
|
want: "age: 30\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with nested structure",
|
||||||
|
format: "yaml",
|
||||||
|
value: map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"age": 30,
|
||||||
|
"name": "John Doe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "user:\n age: 30\n name: John Doe\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with yaml.Marshaler",
|
||||||
|
format: "yaml",
|
||||||
|
value: &mockYAMLMarshaler{val: map[string]int{"age": 30}},
|
||||||
|
want: "age: 30\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with error from yaml.Marshaler",
|
||||||
|
format: "yaml",
|
||||||
|
value: &mockYAMLMarshaler{err: errors.New("mock error")},
|
||||||
|
wantErr: "render: failed: mock error",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with error writing to writer",
|
||||||
|
format: "yaml",
|
||||||
|
writeErr: errors.New("write error!!1"),
|
||||||
|
value: map[string]int{"age": 30},
|
||||||
|
wantErr: "render: failed: yaml: write error: write error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with invalid type",
|
||||||
|
format: "yaml",
|
||||||
|
value: make(chan int),
|
||||||
|
wantPanic: "cannot marshal type: chan int",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestRender(t *testing.T) {
|
func TestRender(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []renderFormatTestCase{}
|
||||||
name string
|
tests = append(tests, jsonFormatTestCases...)
|
||||||
writeErr error
|
tests = append(tests, textFormatTestCases...)
|
||||||
format string
|
tests = append(tests, yamlFormatTestCases...)
|
||||||
value any
|
|
||||||
want string
|
|
||||||
wantErr string
|
|
||||||
wantErrIs []error
|
|
||||||
wantPanic string
|
|
||||||
}{
|
|
||||||
// "bin" format.
|
|
||||||
{
|
|
||||||
name: "bin format with binary marshaler",
|
|
||||||
format: "bin",
|
|
||||||
value: &mockBinaryMarshaler{data: []byte("test string")},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bin format without binary marshaler",
|
|
||||||
format: "bin",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bin format with error marshaling",
|
|
||||||
format: "bin",
|
|
||||||
value: &mockBinaryMarshaler{
|
|
||||||
data: []byte("test string"),
|
|
||||||
err: errors.New("marshal error!!1"),
|
|
||||||
},
|
|
||||||
wantErr: "render: marshal error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bin format with error writing to writer",
|
|
||||||
format: "bin",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: &mockBinaryMarshaler{data: []byte("test string")},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bin format with invalid type",
|
|
||||||
format: "bin",
|
|
||||||
value: make(chan int),
|
|
||||||
wantErr: "render: cannot render",
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
// "binary" format.
|
|
||||||
{
|
|
||||||
name: "binary format with binary marshaler",
|
|
||||||
format: "binary",
|
|
||||||
value: &mockBinaryMarshaler{data: []byte("test string")},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "binary format without binary marshaler",
|
|
||||||
format: "binary",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "binary format with error marshaling",
|
|
||||||
format: "binary",
|
|
||||||
value: &mockBinaryMarshaler{
|
|
||||||
data: []byte("test string"),
|
|
||||||
err: errors.New("marshal error!!1"),
|
|
||||||
},
|
|
||||||
wantErr: "render: marshal error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "binary format with error writing to writer",
|
|
||||||
format: "binary",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: &mockBinaryMarshaler{data: []byte("test string")},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "binary format with invalid type",
|
|
||||||
format: "binary",
|
|
||||||
value: make(chan int),
|
|
||||||
wantErr: "render: cannot render",
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
// "json" format.
|
|
||||||
{
|
|
||||||
name: "json format",
|
|
||||||
format: "json",
|
|
||||||
value: map[string]int{"age": 30},
|
|
||||||
want: "{\n \"age\": 30\n}\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "json format with json marshaler",
|
|
||||||
format: "json",
|
|
||||||
value: &mockJSONMarshaler{data: []byte(`{"age":30}`)},
|
|
||||||
want: "{\n \"age\": 30\n}\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "json format with error from json marshaler",
|
|
||||||
format: "json",
|
|
||||||
value: &mockJSONMarshaler{err: errors.New("marshal error!!1")},
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "json format with error writing to writer",
|
|
||||||
format: "json",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: map[string]int{"age": 30},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "json format with invalid type",
|
|
||||||
format: "json",
|
|
||||||
value: make(chan int),
|
|
||||||
wantErr: "render: json: unsupported type: chan int",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
// "plain" format.
|
|
||||||
{
|
|
||||||
name: "plain format with fmt.Stringer",
|
|
||||||
format: "plain",
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "plain format with io.WriterTo",
|
|
||||||
format: "plain",
|
|
||||||
value: &mockWriterTo{value: "test string"},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "plain format without fmt.Stringer or io.WriterTo",
|
|
||||||
format: "plain",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "plain format with error writing to writer",
|
|
||||||
format: "plain",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "plain format with error from io.WriterTo",
|
|
||||||
format: "plain",
|
|
||||||
value: &mockWriterTo{
|
|
||||||
value: "test string",
|
|
||||||
err: errors.New("WriteTo error!!1"),
|
|
||||||
},
|
|
||||||
wantErr: "render: WriteTo error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "plain format with invalid type",
|
|
||||||
format: "plain",
|
|
||||||
value: make(chan int),
|
|
||||||
wantErr: "render: cannot render",
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
// "text" format.
|
|
||||||
{
|
|
||||||
name: "text format with fmt.Stringer",
|
|
||||||
format: "text",
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "text format with io.WriterTo",
|
|
||||||
format: "text",
|
|
||||||
value: &mockWriterTo{value: "test string"},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "text format without fmt.Stringer or io.WriterTo",
|
|
||||||
format: "text",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "text format with error writing to writer",
|
|
||||||
format: "text",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "text format with error from io.WriterTo",
|
|
||||||
format: "text",
|
|
||||||
value: &mockWriterTo{
|
|
||||||
value: "test string",
|
|
||||||
err: errors.New("WriteTo error!!1"),
|
|
||||||
},
|
|
||||||
wantErr: "render: WriteTo error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "text format with invalid type",
|
|
||||||
format: "text",
|
|
||||||
value: make(chan int),
|
|
||||||
wantErr: "render: cannot render",
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
// "txt" format.
|
|
||||||
{
|
|
||||||
name: "txt format with fmt.Stringer",
|
|
||||||
format: "txt",
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "txt format with io.WriterTo",
|
|
||||||
format: "txt",
|
|
||||||
value: &mockWriterTo{value: "test string"},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "txt format without fmt.Stringer or io.WriterTo",
|
|
||||||
format: "txt",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "txt format with error writing to writer",
|
|
||||||
format: "txt",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "txt format with error from io.WriterTo",
|
|
||||||
format: "txt",
|
|
||||||
value: &mockWriterTo{
|
|
||||||
value: "test string",
|
|
||||||
err: errors.New("WriteTo error!!1"),
|
|
||||||
},
|
|
||||||
wantErr: "render: WriteTo error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "txt format with invalid type",
|
|
||||||
format: "txt",
|
|
||||||
value: make(chan int),
|
|
||||||
wantErr: "render: cannot render",
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
// "xml" format.
|
|
||||||
{
|
|
||||||
name: "xml format",
|
|
||||||
format: "xml",
|
|
||||||
value: struct {
|
|
||||||
XMLName xml.Name `xml:"user"`
|
|
||||||
Age int `xml:"age"`
|
|
||||||
}{Age: 30},
|
|
||||||
want: "<user>\n <age>30</age>\n</user>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "xml format with xml.Marshaler",
|
|
||||||
format: "xml",
|
|
||||||
value: &mockXMLMarshaler{elm: "test string"},
|
|
||||||
want: "<mockXMLMarshaler>test string</mockXMLMarshaler>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "xml format with error from xml.Marshaler",
|
|
||||||
format: "xml",
|
|
||||||
value: &mockXMLMarshaler{err: errors.New("marshal error!!1")},
|
|
||||||
wantErr: "render: marshal error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "xml format with error writing to writer",
|
|
||||||
format: "xml",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: struct {
|
|
||||||
XMLName xml.Name `xml:"user"`
|
|
||||||
Age int `xml:"age"`
|
|
||||||
}{Age: 30},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "xml format with invalid value",
|
|
||||||
format: "xml",
|
|
||||||
value: make(chan int),
|
|
||||||
wantErr: "render: xml: unsupported type: chan int",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
// "yaml" format.
|
|
||||||
{
|
|
||||||
name: "yaml format",
|
|
||||||
format: "yaml",
|
|
||||||
value: map[string]int{"age": 30},
|
|
||||||
want: "age: 30\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yaml format with nested structure",
|
|
||||||
format: "yaml",
|
|
||||||
value: map[string]any{
|
|
||||||
"user": map[string]any{
|
|
||||||
"age": 30,
|
|
||||||
"name": "John Doe",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: "user:\n age: 30\n name: John Doe\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yaml format with yaml.Marshaler",
|
|
||||||
format: "yaml",
|
|
||||||
value: &mockYAMLMarshaler{val: map[string]int{"age": 30}},
|
|
||||||
want: "age: 30\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yaml format with error from yaml.Marshaler",
|
|
||||||
format: "yaml",
|
|
||||||
value: &mockYAMLMarshaler{err: errors.New("mock error")},
|
|
||||||
wantErr: "render: mock error",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yaml format with error writing to writer",
|
|
||||||
format: "yaml",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: map[string]int{"age": 30},
|
|
||||||
wantErr: "render: yaml: write error: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yaml format with invalid type",
|
|
||||||
format: "yaml",
|
|
||||||
value: make(chan int),
|
|
||||||
wantPanic: "cannot marshal type: chan int",
|
|
||||||
},
|
|
||||||
// "yml" format.
|
|
||||||
{
|
|
||||||
name: "yml format",
|
|
||||||
format: "yml",
|
|
||||||
value: map[string]int{"age": 30},
|
|
||||||
want: "age: 30\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yml format with nested structure",
|
|
||||||
format: "yml",
|
|
||||||
value: map[string]any{
|
|
||||||
"user": map[string]any{
|
|
||||||
"age": 30,
|
|
||||||
"name": "John Doe",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: "user:\n age: 30\n name: John Doe\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yml format with yaml.Marshaler",
|
|
||||||
format: "yml",
|
|
||||||
value: &mockYAMLMarshaler{val: map[string]int{"age": 30}},
|
|
||||||
want: "age: 30\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yml format with error from yaml.Marshaler",
|
|
||||||
format: "yml",
|
|
||||||
value: &mockYAMLMarshaler{err: errors.New("mock error")},
|
|
||||||
wantErr: "render: mock error",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yml format with error writing to writer",
|
|
||||||
format: "yml",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: map[string]int{"age": 30},
|
|
||||||
wantErr: "render: yaml: write error: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yml format with invalid type",
|
|
||||||
format: "yml",
|
|
||||||
value: make(chan int),
|
|
||||||
wantPanic: "cannot marshal type: chan int",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
w := &mockWriter{WriteErr: tt.writeErr}
|
w := &mockWriter{WriteErr: tt.writeErr}
|
||||||
|
|||||||
53
renderer.go
Normal file
53
renderer.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnsupportedFormat is returned when a format is not supported by a
|
||||||
|
// renderer. Any method that accepts a format string may return this error.
|
||||||
|
var ErrUnsupportedFormat = fmt.Errorf("%w: unsupported format", Err)
|
||||||
|
|
||||||
|
// Renderer is a renderer that delegates rendering to another renderer
|
||||||
|
// based on a format value.
|
||||||
|
type Renderer struct {
|
||||||
|
// Formats is a map of format names to renderers. When Render is called,
|
||||||
|
// the format is used to look up the renderer to use.
|
||||||
|
Formats map[string]FormatRenderer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFormatRenderer returns a new FormatRenderer that delegates rendering to
|
||||||
|
// the specified renderers.
|
||||||
|
func NewFormatRenderer(formats map[string]FormatRenderer) *Renderer {
|
||||||
|
return &Renderer{Formats: formats}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render renders a value to an io.Writer using the specified format. If the
|
||||||
|
// format is not supported, ErrUnsupportedFormat is returned.
|
||||||
|
//
|
||||||
|
// If the format is supported, but the value cannot be rendered to the format,
|
||||||
|
// the error returned by the renderer is returned. In most cases this will be
|
||||||
|
// ErrCannotRender, but it could be a different error if the renderer returns
|
||||||
|
// one.
|
||||||
|
func (r *Renderer) Render(w io.Writer, format string, v any) error {
|
||||||
|
renderer, ok := r.Formats[format]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnsupportedFormat, format)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := renderer.Render(w, v)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrCannotRender) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnsupportedFormat, format)
|
||||||
|
}
|
||||||
|
if !errors.Is(err, ErrFailed) {
|
||||||
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
138
renderer_test.go
Normal file
138
renderer_test.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package render_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jimeh/go-render"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRenderer_Render(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
renderers map[string]render.FormatRenderer
|
||||||
|
format string
|
||||||
|
value interface{}
|
||||||
|
want string
|
||||||
|
wantErr string
|
||||||
|
wantErrIs []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "existing renderer",
|
||||||
|
renderers: map[string]render.FormatRenderer{
|
||||||
|
"mock": &mockRenderer{output: "mock output"},
|
||||||
|
},
|
||||||
|
format: "mock",
|
||||||
|
value: struct{}{},
|
||||||
|
want: "mock output",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing renderer returns error",
|
||||||
|
renderers: map[string]render.FormatRenderer{
|
||||||
|
"other": &mockRenderer{
|
||||||
|
output: "mock output",
|
||||||
|
err: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: "other",
|
||||||
|
value: struct{}{},
|
||||||
|
wantErr: "render: failed: mock error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing renderer returns ErrCannotRender",
|
||||||
|
renderers: map[string]render.FormatRenderer{
|
||||||
|
"other": &mockRenderer{
|
||||||
|
output: "mock output",
|
||||||
|
err: fmt.Errorf("%w: mock", render.ErrCannotRender),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: "other",
|
||||||
|
value: struct{}{},
|
||||||
|
wantErr: "render: unsupported format: other",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existing renderer",
|
||||||
|
renderers: map[string]render.FormatRenderer{},
|
||||||
|
format: "unknown",
|
||||||
|
value: struct{}{},
|
||||||
|
wantErr: "render: unsupported format: unknown",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
fr := &render.Renderer{
|
||||||
|
Formats: tt.renderers,
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
err := fr.Render(&buf, tt.format, tt.value)
|
||||||
|
got := buf.String()
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
|
}
|
||||||
|
for _, e := range tt.wantErrIs {
|
||||||
|
assert.ErrorIs(t, err, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderer_RenderAllFormats(t *testing.T) {
|
||||||
|
tests := []renderFormatTestCase{}
|
||||||
|
tests = append(tests, binaryFormattestCases...)
|
||||||
|
tests = append(tests, jsonFormatTestCases...)
|
||||||
|
tests = append(tests, textFormatTestCases...)
|
||||||
|
tests = append(tests, xmlFormatTestCases...)
|
||||||
|
tests = append(tests, yamlFormatTestCases...)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
w := &mockWriter{WriteErr: tt.writeErr}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var panicRes any
|
||||||
|
renderer, err := render.New("binary", "json", "text", "xml", "yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
panicRes = r
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err = renderer.Render(w, tt.format, tt.value)
|
||||||
|
}()
|
||||||
|
|
||||||
|
got := w.String()
|
||||||
|
|
||||||
|
if tt.wantPanic != "" {
|
||||||
|
assert.Equal(t, tt.wantPanic, panicRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
|
}
|
||||||
|
for _, e := range tt.wantErrIs {
|
||||||
|
assert.ErrorIs(t, err, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantPanic == "" &&
|
||||||
|
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
28
stringer.go
28
stringer.go
@@ -1,28 +0,0 @@
|
|||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Stringer is a renderer that renders a value to an io.Writer using the
|
|
||||||
// String method.
|
|
||||||
type Stringer struct{}
|
|
||||||
|
|
||||||
var _ Renderer = (*Stringer)(nil)
|
|
||||||
|
|
||||||
// Render renders a value to an io.Writer using the String method. If the value
|
|
||||||
// does not implement fmt.Stringer, ErrCannotRender is returned.
|
|
||||||
func (s *Stringer) Render(w io.Writer, v any) error {
|
|
||||||
x, ok := v.(fmt.Stringer)
|
|
||||||
if !ok {
|
|
||||||
return ErrCannotRender
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := w.Write([]byte(x.String()))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", Err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package render_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/jimeh/go-render"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockStringer struct {
|
|
||||||
value string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fmt.Stringer = (*mockStringer)(nil)
|
|
||||||
|
|
||||||
func (ms *mockStringer) String() string {
|
|
||||||
return ms.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringer_Render(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
writeErr error
|
|
||||||
value any
|
|
||||||
want string
|
|
||||||
wantErr string
|
|
||||||
wantErrIs []error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "implements fmt.Stringer",
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
want: "test string",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "does not implement fmt.Stringer",
|
|
||||||
value: struct{}{},
|
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "error writing to writer",
|
|
||||||
writeErr: errors.New("write error!!1"),
|
|
||||||
value: &mockStringer{value: "test string"},
|
|
||||||
wantErr: "render: write error!!1",
|
|
||||||
wantErrIs: []error{render.Err},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s := &render.Stringer{}
|
|
||||||
w := &mockWriter{WriteErr: tt.writeErr}
|
|
||||||
|
|
||||||
err := s.Render(w, tt.value)
|
|
||||||
got := w.String()
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
assert.EqualError(t, err, tt.wantErr)
|
|
||||||
}
|
|
||||||
for _, e := range tt.wantErrIs {
|
|
||||||
assert.ErrorIs(t, err, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.want, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
text.go
Normal file
28
text.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Text struct{}
|
||||||
|
|
||||||
|
var _ FormatRenderer = (*Text)(nil)
|
||||||
|
|
||||||
|
func (t *Text) Render(w io.Writer, v any) error {
|
||||||
|
var err error
|
||||||
|
switch x := v.(type) {
|
||||||
|
case fmt.Stringer:
|
||||||
|
_, err = w.Write([]byte(x.String()))
|
||||||
|
case io.WriterTo:
|
||||||
|
_, err = x.WriteTo(w)
|
||||||
|
default:
|
||||||
|
return ErrCannotRender
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package render_test
|
package render_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -10,6 +10,16 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type mockStringer struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fmt.Stringer = (*mockStringer)(nil)
|
||||||
|
|
||||||
|
func (ms *mockStringer) String() string {
|
||||||
|
return ms.value
|
||||||
|
}
|
||||||
|
|
||||||
type mockWriterTo struct {
|
type mockWriterTo struct {
|
||||||
value string
|
value string
|
||||||
err error
|
err error
|
||||||
@@ -27,7 +37,7 @@ func (m *mockWriterTo) WriteTo(w io.Writer) (int64, error) {
|
|||||||
return int64(n), err
|
return int64(n), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriterTo_Render(t *testing.T) {
|
func TestText_Render(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
writeErr error
|
writeErr error
|
||||||
@@ -36,33 +46,45 @@ func TestWriterTo_Render(t *testing.T) {
|
|||||||
wantErr string
|
wantErr string
|
||||||
wantErrIs []error
|
wantErrIs []error
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "implements fmt.Stringer",
|
||||||
|
value: &mockStringer{value: "test string"},
|
||||||
|
want: "test string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error writing to writer with fmt.Stringer",
|
||||||
|
writeErr: errors.New("write error!!1"),
|
||||||
|
value: &mockStringer{value: "test string"},
|
||||||
|
wantErr: "render: failed: write error!!1",
|
||||||
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "implements io.WriterTo",
|
name: "implements io.WriterTo",
|
||||||
value: &mockWriterTo{value: "test string"},
|
value: &mockWriterTo{value: "test string"},
|
||||||
want: "test string",
|
want: "test string",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "does not implement io.WriterTo",
|
name: "does not implement fmt.Stringer or io.WriterTo",
|
||||||
value: struct{}{},
|
value: struct{}{},
|
||||||
wantErr: "render: cannot render",
|
wantErr: "render: cannot render",
|
||||||
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
wantErrIs: []error{render.Err, render.ErrCannotRender},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error writing to writer",
|
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"),
|
||||||
},
|
},
|
||||||
wantErr: "render: WriteTo error!!1",
|
wantErr: "render: failed: WriteTo error!!1",
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
wt := &render.WriterTo{}
|
s := &render.Text{}
|
||||||
w := &bytes.Buffer{}
|
w := &mockWriter{WriteErr: tt.writeErr}
|
||||||
|
|
||||||
err := wt.Render(w, tt.value)
|
err := s.Render(w, tt.value)
|
||||||
got := w.String()
|
got := w.String()
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
28
writer_to.go
28
writer_to.go
@@ -1,28 +0,0 @@
|
|||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WriterTo is a renderer that renders a value to an io.Writer using the
|
|
||||||
// WriteTo method.
|
|
||||||
type WriterTo struct{}
|
|
||||||
|
|
||||||
var _ Renderer = (*WriterTo)(nil)
|
|
||||||
|
|
||||||
// Render renders a value to an io.Writer using the WriteTo method. If the value
|
|
||||||
// does not implement io.WriterTo, ErrCannotRender is returned.
|
|
||||||
func (wt *WriterTo) Render(w io.Writer, v any) error {
|
|
||||||
x, ok := v.(io.WriterTo)
|
|
||||||
if !ok {
|
|
||||||
return ErrCannotRender
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := x.WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %w", Err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
4
xml.go
4
xml.go
@@ -21,7 +21,7 @@ type XML struct {
|
|||||||
Indent string
|
Indent string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Renderer = (*XML)(nil)
|
var _ FormatRenderer = (*XML)(nil)
|
||||||
|
|
||||||
// Render marshals the given value to XML.
|
// Render marshals the given value to XML.
|
||||||
func (x *XML) Render(w io.Writer, v any) error {
|
func (x *XML) Render(w io.Writer, v any) error {
|
||||||
@@ -38,7 +38,7 @@ func (x *XML) Render(w io.Writer, v any) error {
|
|||||||
|
|
||||||
err := enc.Encode(v)
|
err := enc.Encode(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %w", Err, err)
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -89,15 +89,15 @@ func TestXML_Render(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "error from xml.Marshaler",
|
name: "error from xml.Marshaler",
|
||||||
value: &mockXMLMarshaler{err: errors.New("mock error")},
|
value: &mockXMLMarshaler{err: errors.New("mock error")},
|
||||||
wantErr: "render: mock error",
|
wantErr: "render: failed: mock error",
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid value",
|
name: "invalid value",
|
||||||
pretty: false,
|
pretty: false,
|
||||||
value: make(chan int),
|
value: make(chan int),
|
||||||
wantErr: "render: xml: unsupported type: chan int",
|
wantErr: "render: failed: xml: unsupported type: chan int",
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
yaml.go
4
yaml.go
@@ -14,7 +14,7 @@ type YAML struct {
|
|||||||
Indent int
|
Indent int
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Renderer = (*YAML)(nil)
|
var _ FormatRenderer = (*YAML)(nil)
|
||||||
|
|
||||||
// Render marshals the given value to YAML.
|
// Render marshals the given value to YAML.
|
||||||
func (j *YAML) Render(w io.Writer, v any) error {
|
func (j *YAML) Render(w io.Writer, v any) error {
|
||||||
@@ -29,7 +29,7 @@ func (j *YAML) Render(w io.Writer, v any) error {
|
|||||||
|
|
||||||
err := enc.Encode(v)
|
err := enc.Encode(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %w", Err, err)
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ func TestYAML_Render(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "error from yaml.Marshaler",
|
name: "error from yaml.Marshaler",
|
||||||
value: &mockYAMLMarshaler{err: errors.New("mock error")},
|
value: &mockYAMLMarshaler{err: errors.New("mock error")},
|
||||||
wantErr: "render: mock error",
|
wantErr: "render: failed: mock error",
|
||||||
wantErrIs: []error{render.Err},
|
wantErrIs: []error{render.Err, render.ErrFailed},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid value",
|
name: "invalid value",
|
||||||
|
|||||||
Reference in New Issue
Block a user