diff --git a/binary.go b/binary.go
index e9f373e..436c760 100644
--- a/binary.go
+++ b/binary.go
@@ -10,7 +10,7 @@ import (
// interface.
type Binary struct{}
-var _ Renderer = (*Binary)(nil)
+var _ FormatRenderer = (*Binary)(nil)
// Render writes result of calling MarshalBinary() on v. If v does not implment
// 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()
if err != nil {
- return fmt.Errorf("%w: %w", Err, err)
+ return fmt.Errorf("%w: %w", ErrFailed, err)
}
_, err = w.Write(b)
if err != nil {
- return fmt.Errorf("%w: %w", Err, err)
+ return fmt.Errorf("%w: %w", ErrFailed, err)
}
return nil
diff --git a/binary_test.go b/binary_test.go
index 3929b7a..7a7d749 100644
--- a/binary_test.go
+++ b/binary_test.go
@@ -45,15 +45,15 @@ func TestBinary_Render(t *testing.T) {
data: []byte("test string"),
err: errors.New("marshal error!!1"),
},
- wantErr: "render: marshal error!!1",
- wantErrIs: []error{render.Err},
+ wantErr: "render: failed: marshal error!!1",
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
{
name: "error writing to writer",
writeErr: errors.New("write error!!1"),
value: &mockBinaryMarshaler{data: []byte("test string")},
- wantErr: "render: write error!!1",
- wantErrIs: []error{render.Err},
+ wantErr: "render: failed: write error!!1",
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
}
for _, tt := range tests {
diff --git a/format.go b/format.go
deleted file mode 100644
index 96efc19..0000000
--- a/format.go
+++ /dev/null
@@ -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
-}
diff --git a/format_test.go b/format_test.go
deleted file mode 100644
index de94946..0000000
--- a/format_test.go
+++ /dev/null
@@ -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)
- }
- })
- }
-}
diff --git a/json.go b/json.go
index a71051c..6320d0e 100644
--- a/json.go
+++ b/json.go
@@ -21,7 +21,7 @@ type JSON struct {
Indent string
}
-var _ Renderer = (*JSON)(nil)
+var _ FormatRenderer = (*JSON)(nil)
// Render marshals the given value to JSON.
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)
if err != nil {
- return fmt.Errorf("%w: %w", Err, err)
+ return fmt.Errorf("%w: %w", ErrFailed, err)
}
return nil
diff --git a/json_test.go b/json_test.go
index bd78e0a..9e66ac9 100644
--- a/json_test.go
+++ b/json_test.go
@@ -69,14 +69,14 @@ func TestJSON_Render(t *testing.T) {
{
name: "error from json.Marshaler",
value: &mockJSONMarshaler{err: errors.New("marshal error!!1")},
- wantErrIs: []error{render.Err},
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
{
name: "invalid value",
pretty: false,
value: make(chan int),
- wantErr: "render: json: unsupported type: chan int",
- wantErrIs: []error{render.Err},
+ wantErr: "render: failed: json: unsupported type: chan int",
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
}
for _, tt := range tests {
diff --git a/multi.go b/multi.go
index d0f5698..1b01201 100644
--- a/multi.go
+++ b/multi.go
@@ -7,10 +7,10 @@ import (
// MultiRenderer is a renderer that tries multiple renderers until one succeeds.
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,
// ErrCannotRender is returned. If a renderer returns an error that is not
diff --git a/multi_test.go b/multi_test.go
index 5907802..3f533d3 100644
--- a/multi_test.go
+++ b/multi_test.go
@@ -16,7 +16,7 @@ func TestMultiRenderer_Render(t *testing.T) {
tests := []struct {
name string
- renderers []render.Renderer
+ renderers []render.FormatRenderer
value interface{}
want string
wantErr string
@@ -24,7 +24,7 @@ func TestMultiRenderer_Render(t *testing.T) {
}{
{
name: "no renderer can render",
- renderers: []render.Renderer{
+ renderers: []render.FormatRenderer{
cannotRenderer,
cannotRenderer,
},
@@ -33,7 +33,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "one renderer can render",
- renderers: []render.Renderer{
+ renderers: []render.FormatRenderer{
cannotRenderer,
successRenderer,
cannotRenderer,
@@ -43,7 +43,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "multiple renderers can render",
- renderers: []render.Renderer{
+ renderers: []render.FormatRenderer{
&mockRenderer{err: render.ErrCannotRender},
&mockRenderer{output: "first output"},
&mockRenderer{output: "second output"},
@@ -53,7 +53,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "first renderer fails",
- renderers: []render.Renderer{
+ renderers: []render.FormatRenderer{
failRenderer,
successRenderer,
},
@@ -62,7 +62,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "fails after cannot render",
- renderers: []render.Renderer{
+ renderers: []render.FormatRenderer{
cannotRenderer,
failRenderer,
successRenderer,
@@ -72,7 +72,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "fails after success render",
- renderers: []render.Renderer{
+ renderers: []render.FormatRenderer{
successRenderer,
failRenderer,
cannotRenderer,
diff --git a/render.go b/render.go
index 42039d0..cda00da 100644
--- a/render.go
+++ b/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
import (
@@ -6,24 +17,43 @@ import (
)
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
// 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)
)
-// Renderer is the interface that that individual renderers must implement.
-type Renderer interface {
+// FormatRenderer interface is for single format renderers, which can only
+// 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
}
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
// encoding/json package, with pretty printing enabled.
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
// encoding/xml package, with pretty printing enabled.
DefaultXML = &XML{Pretty: true}
@@ -32,39 +62,10 @@ var (
// gopkg.in/yaml.v3 package, with an indentation of 2 spaces.
DefaultYAML = &YAML{Indent: 2}
- // DefaultWriterTo is the default writer to renderer. It renders values
- // using the io.WriterTo interface.
- DefaultWriterTo = &WriterTo{}
-
- // 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,
- }}
+ // DefaultRenderer is used by the package level Render function. It supports
+ // the text", "json", and "yaml" formats. If you need to support another set
+ // of formats, use the New function to create a custom FormatRenderer.
+ DefaultRenderer = MustNew("json", "text", "yaml")
)
// 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:
//
+// - "text": Renders values using the fmt.Stringer and io.WriterTo interfaces.
// - "json": Renders values using the encoding/json package, with pretty
// printing enabled.
// - "yaml": Renders values using the gopkg.in/yaml.v3 package, with an
// 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
// 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 {
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
+}
diff --git a/render_example_test.go b/render_example_test.go
index e1ec840..64f7d37 100644
--- a/render_example_test.go
+++ b/render_example_test.go
@@ -39,7 +39,10 @@ func ExampleRender_json() {
buf := &bytes.Buffer{}
err := render.Render(buf, "json", data)
if err != nil {
- panic(err)
+ fmt.Printf("err: %s\n", err)
+
+ return
+ // panic(err)
}
fmt.Println(buf.String())
@@ -139,9 +142,13 @@ func ExampleRender_xml() {
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.
buf := &bytes.Buffer{}
- err := render.Render(buf, "xml", data)
+ err := r.Render(buf, "xml", data)
if err != nil {
panic(err)
}
diff --git a/render_test.go b/render_test.go
index c5f0f9c..de055e2 100644
--- a/render_test.go
+++ b/render_test.go
@@ -35,7 +35,7 @@ type mockRenderer struct {
err error
}
-var _ render.Renderer = (*mockRenderer)(nil)
+var _ render.FormatRenderer = (*mockRenderer)(nil)
func (m *mockRenderer) Render(w io.Writer, _ any) error {
_, err := w.Write([]byte(m.output))
@@ -59,23 +59,8 @@ func TestDefaultYAML(t *testing.T) {
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) {
- want := &render.MultiRenderer{
- Renderers: []render.Renderer{
- &render.Stringer{},
- &render.WriterTo{},
- },
- }
-
- assert.Equal(t, want, render.DefaultText)
+ assert.Equal(t, &render.Text{}, render.DefaultText)
}
func TestDefaultBinary(t *testing.T) {
@@ -83,408 +68,253 @@ func TestDefaultBinary(t *testing.T) {
}
func TestDefaultRenderer(t *testing.T) {
- want := &render.FormatRenderer{
- Renderers: map[string]render.Renderer{
- "bin": render.DefaultBinary,
- "binary": render.DefaultBinary,
- "json": render.DefaultJSON,
- "plain": render.DefaultText,
- "text": render.DefaultText,
- "txt": render.DefaultText,
- "xml": render.DefaultXML,
- "yaml": render.DefaultYAML,
- "yml": render.DefaultYAML,
+ want := &render.Renderer{
+ Formats: map[string]render.FormatRenderer{
+ "json": render.DefaultJSON,
+ "text": render.DefaultText,
+ "yaml": render.DefaultYAML,
},
}
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: "\n 30\n",
+ },
+ {
+ name: "xml format with xml.Marshaler",
+ format: "xml",
+ value: &mockXMLMarshaler{elm: "test string"},
+ want: "test string",
+ },
+ {
+ 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) {
- tests := []struct {
- name string
- writeErr error
- format string
- 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: "\n 30\n",
- },
- {
- name: "xml format with xml.Marshaler",
- format: "xml",
- value: &mockXMLMarshaler{elm: "test string"},
- want: "test string",
- },
- {
- 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",
- },
- }
+ tests := []renderFormatTestCase{}
+ tests = append(tests, jsonFormatTestCases...)
+ tests = append(tests, textFormatTestCases...)
+ tests = append(tests, yamlFormatTestCases...)
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &mockWriter{WriteErr: tt.writeErr}
diff --git a/renderer.go b/renderer.go
new file mode 100644
index 0000000..0acf58a
--- /dev/null
+++ b/renderer.go
@@ -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
+}
diff --git a/renderer_test.go b/renderer_test.go
new file mode 100644
index 0000000..e971892
--- /dev/null
+++ b/renderer_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/stringer.go b/stringer.go
deleted file mode 100644
index 51ee7e0..0000000
--- a/stringer.go
+++ /dev/null
@@ -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
-}
diff --git a/stringer_test.go b/stringer_test.go
deleted file mode 100644
index b328e71..0000000
--- a/stringer_test.go
+++ /dev/null
@@ -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)
- }
- })
- }
-}
diff --git a/text.go b/text.go
new file mode 100644
index 0000000..ebad65b
--- /dev/null
+++ b/text.go
@@ -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
+}
diff --git a/writer_to_test.go b/text_test.go
similarity index 58%
rename from writer_to_test.go
rename to text_test.go
index 709109b..46edf9a 100644
--- a/writer_to_test.go
+++ b/text_test.go
@@ -1,8 +1,8 @@
package render_test
import (
- "bytes"
"errors"
+ "fmt"
"io"
"testing"
@@ -10,6 +10,16 @@ import (
"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 {
value string
err error
@@ -27,7 +37,7 @@ func (m *mockWriterTo) WriteTo(w io.Writer) (int64, error) {
return int64(n), err
}
-func TestWriterTo_Render(t *testing.T) {
+func TestText_Render(t *testing.T) {
tests := []struct {
name string
writeErr error
@@ -36,33 +46,45 @@ func TestWriterTo_Render(t *testing.T) {
wantErr string
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",
value: &mockWriterTo{value: "test string"},
want: "test string",
},
{
- name: "does not implement io.WriterTo",
+ name: "does not implement fmt.Stringer or io.WriterTo",
value: struct{}{},
wantErr: "render: cannot render",
wantErrIs: []error{render.Err, render.ErrCannotRender},
},
{
- name: "error writing to writer",
+ name: "error writing to writer with io.WriterTo",
value: &mockWriterTo{
value: "test string",
err: errors.New("WriteTo error!!1"),
},
- wantErr: "render: WriteTo error!!1",
- wantErrIs: []error{render.Err},
+ wantErr: "render: failed: WriteTo error!!1",
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- wt := &render.WriterTo{}
- w := &bytes.Buffer{}
+ s := &render.Text{}
+ w := &mockWriter{WriteErr: tt.writeErr}
- err := wt.Render(w, tt.value)
+ err := s.Render(w, tt.value)
got := w.String()
if tt.wantErr != "" {
diff --git a/writer_to.go b/writer_to.go
deleted file mode 100644
index 80f023b..0000000
--- a/writer_to.go
+++ /dev/null
@@ -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
-}
diff --git a/xml.go b/xml.go
index a601a3e..8fd8c07 100644
--- a/xml.go
+++ b/xml.go
@@ -21,7 +21,7 @@ type XML struct {
Indent string
}
-var _ Renderer = (*XML)(nil)
+var _ FormatRenderer = (*XML)(nil)
// Render marshals the given value to XML.
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)
if err != nil {
- return fmt.Errorf("%w: %w", Err, err)
+ return fmt.Errorf("%w: %w", ErrFailed, err)
}
return nil
diff --git a/xml_test.go b/xml_test.go
index 7f28913..2e5b33f 100644
--- a/xml_test.go
+++ b/xml_test.go
@@ -89,15 +89,15 @@ func TestXML_Render(t *testing.T) {
{
name: "error from xml.Marshaler",
value: &mockXMLMarshaler{err: errors.New("mock error")},
- wantErr: "render: mock error",
- wantErrIs: []error{render.Err},
+ wantErr: "render: failed: mock error",
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
{
name: "invalid value",
pretty: false,
value: make(chan int),
- wantErr: "render: xml: unsupported type: chan int",
- wantErrIs: []error{render.Err},
+ wantErr: "render: failed: xml: unsupported type: chan int",
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
}
diff --git a/yaml.go b/yaml.go
index fea91be..46b2f3b 100644
--- a/yaml.go
+++ b/yaml.go
@@ -14,7 +14,7 @@ type YAML struct {
Indent int
}
-var _ Renderer = (*YAML)(nil)
+var _ FormatRenderer = (*YAML)(nil)
// Render marshals the given value to YAML.
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)
if err != nil {
- return fmt.Errorf("%w: %w", Err, err)
+ return fmt.Errorf("%w: %w", ErrFailed, err)
}
return nil
diff --git a/yaml_test.go b/yaml_test.go
index 2b930dd..27f2b38 100644
--- a/yaml_test.go
+++ b/yaml_test.go
@@ -66,8 +66,8 @@ func TestYAML_Render(t *testing.T) {
{
name: "error from yaml.Marshaler",
value: &mockYAMLMarshaler{err: errors.New("mock error")},
- wantErr: "render: mock error",
- wantErrIs: []error{render.Err},
+ wantErr: "render: failed: mock error",
+ wantErrIs: []error{render.Err, render.ErrFailed},
},
{
name: "invalid value",