diff --git a/.golangci.yml b/.golangci.yml
index d22a7ee..450a48b 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -2,6 +2,8 @@ linters-settings:
funlen:
lines: 100
statements: 150
+ goconst:
+ min-occurrences: 5
gocyclo:
min-complexity: 20
golint:
diff --git a/binary.go b/binary.go
index d27c6ec..5fd3797 100644
--- a/binary.go
+++ b/binary.go
@@ -14,7 +14,7 @@ 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.
-func (bm *Binary) Render(w io.Writer, v any) error {
+func (br *Binary) Render(w io.Writer, v any) error {
x, ok := v.(encoding.BinaryMarshaler)
if !ok {
return fmt.Errorf("%w: %T", ErrCannotRender, v)
@@ -32,3 +32,7 @@ func (bm *Binary) Render(w io.Writer, v any) error {
return nil
}
+
+func (br *Binary) Formats() []string {
+ return []string{"binary", "bin"}
+}
diff --git a/binary_test.go b/binary_test.go
index f8e0cf2..f3be967 100644
--- a/binary_test.go
+++ b/binary_test.go
@@ -1,11 +1,10 @@
-package render_test
+package render
import (
"encoding"
"errors"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
)
@@ -38,7 +37,7 @@ func TestBinary_Render(t *testing.T) {
name: "does not implement encoding.BinaryMarshaler",
value: struct{}{},
wantErr: "render: cannot render: struct {}",
- wantErrIs: []error{render.Err, render.ErrCannotRender},
+ wantErrIs: []error{Err, ErrCannotRender},
},
{
name: "error marshaling",
@@ -47,19 +46,19 @@ func TestBinary_Render(t *testing.T) {
err: errors.New("marshal error!!1"),
},
wantErr: "render: failed: marshal error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "error writing to writer",
writeErr: errors.New("write error!!1"),
value: &mockBinaryMarshaler{data: []byte("test string")},
wantErr: "render: failed: write error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- b := &render.Binary{}
+ b := &Binary{}
w := &mockWriter{WriteErr: tt.writeErr}
err := b.Render(w, tt.value)
diff --git a/json.go b/json.go
index 6320d0e..ad3eccd 100644
--- a/json.go
+++ b/json.go
@@ -24,11 +24,11 @@ type JSON struct {
var _ FormatRenderer = (*JSON)(nil)
// Render marshals the given value to JSON.
-func (j *JSON) Render(w io.Writer, v any) error {
+func (jr *JSON) Render(w io.Writer, v any) error {
enc := json.NewEncoder(w)
- if j.Pretty {
- prefix := j.Prefix
- indent := j.Indent
+ if jr.Pretty {
+ prefix := jr.Prefix
+ indent := jr.Indent
if indent == "" {
indent = " "
}
@@ -43,3 +43,7 @@ func (j *JSON) Render(w io.Writer, v any) error {
return nil
}
+
+func (jr *JSON) Formats() []string {
+ return []string{"json"}
+}
diff --git a/json_test.go b/json_test.go
index 9e66ac9..9bba39b 100644
--- a/json_test.go
+++ b/json_test.go
@@ -1,4 +1,4 @@
-package render_test
+package render
import (
"bytes"
@@ -6,7 +6,6 @@ import (
"errors"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -69,19 +68,19 @@ func TestJSON_Render(t *testing.T) {
{
name: "error from json.Marshaler",
value: &mockJSONMarshaler{err: errors.New("marshal error!!1")},
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "invalid value",
pretty: false,
value: make(chan int),
wantErr: "render: failed: json: unsupported type: chan int",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- j := &render.JSON{
+ j := &JSON{
Pretty: tt.pretty,
Prefix: tt.prefix,
Indent: tt.indent,
diff --git a/multi.go b/multi.go
index 4014176..35fb6e2 100644
--- a/multi.go
+++ b/multi.go
@@ -6,17 +6,17 @@ import (
"io"
)
-// MultiRenderer is a renderer that tries multiple renderers until one succeeds.
-type MultiRenderer struct {
+// Multi is a renderer that tries multiple renderers until one succeeds.
+type Multi struct {
Renderers []FormatRenderer
}
-var _ FormatRenderer = (*MultiRenderer)(nil)
+var _ FormatRenderer = (*Multi)(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
// ErrCannotRender, that error is returned.
-func (mr *MultiRenderer) Render(w io.Writer, v any) error {
+func (mr *Multi) Render(w io.Writer, v any) error {
for _, r := range mr.Renderers {
err := r.Render(w, v)
if err == nil {
@@ -29,3 +29,22 @@ func (mr *MultiRenderer) Render(w io.Writer, v any) error {
return fmt.Errorf("%w: %T", ErrCannotRender, v)
}
+
+func (mr *Multi) Formats() []string {
+ formats := make(map[string]struct{})
+
+ for _, r := range mr.Renderers {
+ if x, ok := r.(Formats); ok {
+ for _, f := range x.Formats() {
+ formats[f] = struct{}{}
+ }
+ }
+ }
+
+ result := make([]string, 0, len(formats))
+ for f := range formats {
+ result = append(result, f)
+ }
+
+ return result
+}
diff --git a/multi_test.go b/multi_test.go
index 268a80c..1c258d5 100644
--- a/multi_test.go
+++ b/multi_test.go
@@ -1,22 +1,21 @@
-package render_test
+package render
import (
"bytes"
"errors"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
)
func TestMultiRenderer_Render(t *testing.T) {
successRenderer := &mockRenderer{output: "success output"}
- cannotRenderer := &mockRenderer{err: render.ErrCannotRender}
+ cannotRenderer := &mockRenderer{err: ErrCannotRender}
failRenderer := &mockRenderer{err: errors.New("mock error")}
tests := []struct {
name string
- renderers []render.FormatRenderer
+ renderers []FormatRenderer
value interface{}
want string
wantErr string
@@ -24,17 +23,17 @@ func TestMultiRenderer_Render(t *testing.T) {
}{
{
name: "no renderer can render",
- renderers: []render.FormatRenderer{
+ renderers: []FormatRenderer{
cannotRenderer,
cannotRenderer,
},
value: "test",
wantErr: "render: cannot render: string",
- wantErrIs: []error{render.ErrCannotRender},
+ wantErrIs: []error{ErrCannotRender},
},
{
name: "one renderer can render",
- renderers: []render.FormatRenderer{
+ renderers: []FormatRenderer{
cannotRenderer,
successRenderer,
cannotRenderer,
@@ -44,8 +43,8 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "multiple renderers can render",
- renderers: []render.FormatRenderer{
- &mockRenderer{err: render.ErrCannotRender},
+ renderers: []FormatRenderer{
+ &mockRenderer{err: ErrCannotRender},
&mockRenderer{output: "first output"},
&mockRenderer{output: "second output"},
},
@@ -54,7 +53,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "first renderer fails",
- renderers: []render.FormatRenderer{
+ renderers: []FormatRenderer{
failRenderer,
successRenderer,
},
@@ -63,7 +62,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "fails after cannot render",
- renderers: []render.FormatRenderer{
+ renderers: []FormatRenderer{
cannotRenderer,
failRenderer,
successRenderer,
@@ -73,7 +72,7 @@ func TestMultiRenderer_Render(t *testing.T) {
},
{
name: "fails after success render",
- renderers: []render.FormatRenderer{
+ renderers: []FormatRenderer{
successRenderer,
failRenderer,
cannotRenderer,
@@ -85,7 +84,7 @@ func TestMultiRenderer_Render(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- mr := &render.MultiRenderer{
+ mr := &Multi{
Renderers: tt.renderers,
}
var buf bytes.Buffer
diff --git a/render.go b/render.go
index f234ce5..8d25904 100644
--- a/render.go
+++ b/render.go
@@ -39,101 +39,134 @@ type FormatRenderer interface {
Render(w io.Writer, v any) error
}
+// Formats is an optional interface that can be implemented by FormatRenderer
+// implementations to return a list of formats that the renderer supports. This
+// is used by the NewRenderer function to allowing format aliases like "yml" for
+// "yaml".
+type Formats interface {
+ Formats() []string
+}
+
var (
- // DefaultBinary is the default binary marshaler renderer. It
- // renders values using the encoding.BinaryMarshaler interface.
- DefaultBinary = &Binary{}
+ prettyRenderer = New(map[string]FormatRenderer{
+ "binary": &Binary{},
+ "json": &JSON{Pretty: true},
+ "text": &Text{},
+ "xml": &XML{Pretty: true},
+ "yaml": &YAML{Indent: 2},
+ })
+ compactRenderer = New(map[string]FormatRenderer{
+ "binary": &Binary{},
+ "json": &JSON{},
+ "text": &Text{},
+ "xml": &XML{},
+ "yaml": &YAML{},
+ })
- // 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}
-
- // DefaultYAML is the default YAML renderer. It renders values using the
- // gopkg.in/yaml.v3 package, with an indentation of 2 spaces.
- DefaultYAML = &YAML{Indent: 2}
-
- // 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")
+ DefaultPretty = prettyRenderer.OnlyWith("json", "text", "xml", "yaml")
+ DefaultCompact = compactRenderer.OnlyWith("json", "text", "xml", "yaml")
)
// Render renders the given value to the given writer using the given format.
+// If pretty is true, the value will be rendered in a pretty way, otherwise it
+// will be rendered in a compact way.
+//
+// By default it supports the following formats:
+//
+// - "text": Renders values via a myriad of ways.
+// - "json": Renders values using the encoding/json package.
+// - "yaml": Renders values using the gopkg.in/yaml.v3 package.
+// - "xml": Renders values using the encoding/xml package.
+//
+// If the format is not supported, a ErrUnsupportedFormat error will be
+// returned.
+func Render(w io.Writer, format string, pretty bool, v any) error {
+ if pretty {
+ return DefaultPretty.Render(w, format, v)
+ }
+
+ return DefaultCompact.Render(w, format, v)
+}
+
+// Pretty renders the given value to the given writer using the given format.
// The format must be one of the formats supported by the default renderer.
//
// By default it supports the following formats:
//
-// - "text": Renders values using the fmt.Stringer and io.WriterTo interfaces.
+// - "text": Renders values via a myriad of ways.
// - "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.
+// - "xml": Renders values using the encoding/xml package, with pretty
+// printing enabled.
//
// 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)
+// create a new Renderer with the formats you need. If you need new custom
+// renderers, manually create a new Renderer.
+func Pretty(w io.Writer, format string, v any) error {
+ return DefaultPretty.Render(w, format, v)
}
-// New creates a new *FormatRenderer with support for the given formats.
+// Compact renders the given value to the given writer using the given format.
+// The format must be one of the formats supported by the default renderer.
//
-// Supported formats are:
+// By default it supports the following formats:
//
-// - "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.
+// - "text": Renders values via a myriad of ways..
+// - "json": Renders values using the encoding/json package.
+// - "yaml": Renders values using the gopkg.in/yaml.v3 package.
+// - "xml": Renders values using the encoding/xml package.
//
-// If an unsupported format is given, an ErrUnsupportedFormat error will be
+// If the format is not supported, a ErrUnsupportedFormat error will be
// returned.
-func New(formats ...string) (*Renderer, error) {
- renderers := map[string]FormatRenderer{}
+//
+// If you need to support a custom set of formats, use the New function to
+// create a new Renderer with the formats you need. If you need new custom
+// renderers, manually create a new Renderer.
+func Compact(w io.Writer, format string, v any) error {
+ return DefaultCompact.Render(w, format, v)
+}
+// NewCompact returns a new renderer which only supports the specified formats
+// and renders structured formats compactly. If no formats are specified, a
+// error is returned.
+//
+// If any of the formats are not supported by, a ErrUnsupported error is
+// returned.
+func NewCompact(formats ...string) (*Renderer, error) {
if len(formats) == 0 {
return nil, fmt.Errorf("%w: no formats specified", Err)
}
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:
+ if _, ok := compactRenderer.Renderers[format]; !ok {
return nil, fmt.Errorf("%w: %s", ErrUnsupportedFormat, format)
}
}
- return NewFormatRenderer(renderers), nil
+ return compactRenderer.OnlyWith(formats...), 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.Error())
+// NewPretty returns a new renderer which only supports the specified formats
+// and renders structured formats in a pretty way. If no formats are specified,
+// a error is returned.
+//
+// If any of the formats are not supported by, a ErrUnsupported error is
+// returned.
+func NewPretty(formats ...string) (*Renderer, error) {
+ if len(formats) == 0 {
+ return nil, fmt.Errorf("%w: no formats specified", Err)
}
- return r
+ for _, format := range formats {
+ if _, ok := prettyRenderer.Renderers[format]; !ok {
+ return nil, fmt.Errorf("%w: %s", ErrUnsupportedFormat, format)
+ }
+ }
+
+ return prettyRenderer.OnlyWith(formats...), nil
}
diff --git a/render_example_test.go b/render_example_test.go
index ba25c4d..34441f4 100644
--- a/render_example_test.go
+++ b/render_example_test.go
@@ -37,7 +37,7 @@ func ExampleRender_json() {
// Render the object to JSON.
buf := &bytes.Buffer{}
- err := render.Render(buf, "json", data)
+ err := render.Pretty(buf, "json", data)
if err != nil {
fmt.Printf("err: %s\n", err)
@@ -96,7 +96,7 @@ func ExampleRender_yaml() {
// Render the object to YAML.
buf := &bytes.Buffer{}
- err := render.Render(buf, "yaml", data)
+ err := render.Pretty(buf, "yaml", data)
if err != nil {
panic(err)
}
@@ -144,7 +144,7 @@ func ExampleRender_xml() {
// Create a new renderer that supports XML in addition to default JSON, YAML
// and Text.
- r := render.MustNew("json", "text", "xml", "yaml")
+ r, _ := render.NewPretty("json", "text", "xml", "yaml")
// Render the object to XML.
buf := &bytes.Buffer{}
@@ -206,7 +206,7 @@ func ExampleRender_textFromByteSlice() {
// Render the object to XML.
buf := &bytes.Buffer{}
- err := render.Render(buf, "text", data)
+ err := render.Pretty(buf, "text", data)
if err != nil {
panic(err)
}
@@ -221,7 +221,7 @@ func ExampleRender_textFromString() {
// Render the object to XML.
buf := &bytes.Buffer{}
- err := render.Render(buf, "text", data)
+ err := render.Pretty(buf, "text", data)
if err != nil {
panic(err)
}
@@ -236,7 +236,7 @@ func ExampleRender_textFromIOReader() {
// Render the object to XML.
buf := &bytes.Buffer{}
- err := render.Render(buf, "text", data)
+ err := render.Pretty(buf, "text", data)
if err != nil {
panic(err)
}
@@ -261,7 +261,7 @@ func ExampleRender_textFromWriterTo() {
// Render the object to XML.
buf := &bytes.Buffer{}
- err := render.Render(buf, "text", data)
+ err := render.Pretty(buf, "text", data)
if err != nil {
panic(err)
}
@@ -294,7 +294,7 @@ func ExampleRender_textFromStringer() {
// Render the object to XML.
buf := &bytes.Buffer{}
- err := render.Render(buf, "text", data)
+ err := render.Pretty(buf, "text", data)
if err != nil {
panic(err)
}
diff --git a/render_test.go b/render_test.go
index f9002ae..27eb43e 100644
--- a/render_test.go
+++ b/render_test.go
@@ -1,13 +1,13 @@
-package render_test
+package render
import (
"bytes"
"encoding/xml"
"errors"
"io"
+ "strings"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
)
@@ -35,7 +35,7 @@ type mockRenderer struct {
err error
}
-var _ render.FormatRenderer = (*mockRenderer)(nil)
+var _ FormatRenderer = (*mockRenderer)(nil)
func (m *mockRenderer) Render(w io.Writer, _ any) error {
_, err := w.Write([]byte(m.output))
@@ -47,231 +47,341 @@ func (m *mockRenderer) Render(w io.Writer, _ any) error {
return err
}
-func TestDefaultJSON(t *testing.T) {
- assert.Equal(t, &render.JSON{Pretty: true}, render.DefaultJSON)
-}
-
-func TestDefaultXML(t *testing.T) {
- assert.Equal(t, &render.XML{Pretty: true}, render.DefaultXML)
-}
-
-func TestDefaultYAML(t *testing.T) {
- assert.Equal(t, &render.YAML{Indent: 2}, render.DefaultYAML)
-}
-
-func TestDefaultText(t *testing.T) {
- assert.Equal(t, &render.Text{}, render.DefaultText)
-}
-
-func TestDefaultBinary(t *testing.T) {
- assert.Equal(t, &render.Binary{}, render.DefaultBinary)
-}
-
-func TestDefaultRenderer(t *testing.T) {
- 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
+ name string
+ writeErr error
+ formats []string
+ value any
+ valueFunc func() any
+ want string
+ wantPretty string
+ wantCompact 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: "with binary marshaler",
+ formats: []string{"binary", "bin"},
+ value: &mockBinaryMarshaler{data: []byte("test string")},
+ want: "test string",
},
{
- name: "binary format without binary marshaler",
- format: "binary",
+ name: "without binary marshaler",
+ formats: []string{"binary", "bin"},
value: struct{}{},
- wantErr: "render: unsupported format: binary",
- wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
+ wantErr: "render: unsupported format: {{format}}",
+ wantErrIs: []error{Err, ErrUnsupportedFormat},
},
{
- name: "binary format with error marshaling",
- format: "binary",
+ name: "with error marshaling",
+ formats: []string{"binary", "bin"},
value: &mockBinaryMarshaler{
data: []byte("test string"),
err: errors.New("marshal error!!1"),
},
wantErr: "render: failed: marshal error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
- name: "binary format with error writing to writer",
- format: "binary",
+ name: "with error writing to writer",
+ formats: []string{"binary", "bin"},
writeErr: errors.New("write error!!1"),
value: &mockBinaryMarshaler{data: []byte("test string")},
wantErr: "render: failed: write error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
- name: "binary format with invalid type",
- format: "binary",
+ name: "with invalid type",
+ formats: []string{"binary", "bin"},
value: make(chan int),
- wantErr: "render: unsupported format: binary",
- wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
+ wantErr: "render: unsupported format: {{format}}",
+ wantErrIs: []error{Err, ErrUnsupportedFormat},
},
}
// "json" format.
var jsonFormatTestCases = []renderFormatTestCase{
{
- name: "json format",
- format: "json",
- value: map[string]int{"age": 30},
- want: "{\n \"age\": 30\n}\n",
+ name: "with map",
+ formats: []string{"json"},
+ value: map[string]int{"age": 30},
+ wantPretty: "{\n \"age\": 30\n}\n",
+ wantCompact: "{\"age\":30}\n",
},
{
- name: "json format with json marshaler",
- format: "json",
- value: &mockJSONMarshaler{data: []byte(`{"age":30}`)},
- want: "{\n \"age\": 30\n}\n",
+ name: "with json marshaler",
+ formats: []string{"json"},
+ value: &mockJSONMarshaler{data: []byte(`{"age":30}`)},
+ wantPretty: "{\n \"age\": 30\n}\n",
+ wantCompact: "{\"age\":30}\n",
},
{
- name: "json format with error from json marshaler",
- format: "json",
+ name: "with error from json marshaler",
+ formats: []string{"json"},
value: &mockJSONMarshaler{err: errors.New("marshal error!!1")},
- wantErrIs: []error{render.Err},
+ wantErrIs: []error{Err},
},
{
- name: "json format with error writing to writer",
- format: "json",
+ name: "with error writing to writer",
+ formats: []string{"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},
+ wantErrIs: []error{Err, ErrFailed},
},
{
- name: "json format with invalid type",
- format: "json",
+ name: "with invalid type",
+ formats: []string{"json"},
value: make(chan int),
wantErr: "render: failed: json: unsupported type: chan int",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
}
// "text" format.
var textFormatTestCases = []renderFormatTestCase{
{
- name: "text format with fmt.Stringer",
- format: "text",
- value: &mockStringer{value: "test string"},
- want: "test string",
+ name: "nil",
+ formats: []string{"text", "txt", "plain"},
+ value: nil,
+ wantErr: "render: unsupported format: {{format}}",
+ wantErrIs: []error{Err, ErrUnsupportedFormat},
},
{
- name: "text format with io.WriterTo",
- format: "text",
- value: &mockWriterTo{value: "test string"},
- want: "test string",
+ name: "byte slice",
+ formats: []string{"text", "txt", "plain"},
+ value: []byte("test byte slice"),
+ want: "test byte slice",
},
{
- 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: "nil byte slice",
+ formats: []string{"text", "txt", "plain"},
+ value: []byte(nil),
+ want: "",
},
{
- name: "text format with error writing to writer",
- format: "text",
+ name: "empty byte slice",
+ formats: []string{"text", "txt", "plain"},
+ value: []byte{},
+ want: "",
+ },
+ {
+ name: "rune slice",
+ formats: []string{"text", "txt", "plain"},
+ value: []rune{'r', 'u', 'n', 'e', 's', '!', ' ', 'y', 'e', 's'},
+ want: "runes! yes",
+ },
+ {
+ name: "string",
+ formats: []string{"text", "txt", "plain"},
+ value: "test string",
+ want: "test string",
+ },
+ {
+ name: "int",
+ formats: []string{"text", "txt", "plain"},
+ value: int(42),
+ want: "42",
+ },
+ {
+ name: "int8",
+ formats: []string{"text", "txt", "plain"},
+ value: int8(43),
+ want: "43",
+ },
+ {
+ name: "int16",
+ formats: []string{"text", "txt", "plain"},
+ value: int16(44),
+ want: "44",
+ },
+ {
+ name: "int32",
+ formats: []string{"text", "txt", "plain"},
+ value: int32(45),
+ want: "45",
+ },
+ {
+ name: "int64",
+ formats: []string{"text", "txt", "plain"},
+ value: int64(46),
+ want: "46",
+ },
+ {
+ name: "uint",
+ formats: []string{"text", "txt", "plain"},
+ value: uint(47),
+ want: "47",
+ },
+ {
+ name: "uint8",
+ formats: []string{"text", "txt", "plain"},
+ value: uint8(48),
+ want: "48",
+ },
+ {
+ name: "uint16",
+ formats: []string{"text", "txt", "plain"},
+ value: uint16(49),
+ want: "49",
+ },
+ {
+ name: "uint32",
+ formats: []string{"text", "txt", "plain"},
+ value: uint32(50),
+ want: "50",
+ },
+ {
+ name: "uint64",
+ formats: []string{"text", "txt", "plain"},
+ value: uint64(51),
+ want: "51",
+ },
+ {
+ name: "float32",
+ formats: []string{"text", "txt", "plain"},
+ value: float32(3.14),
+ want: "3.14",
+ },
+ {
+ name: "float64",
+ formats: []string{"text", "txt", "plain"},
+ value: float64(3.14159),
+ want: "3.14159",
+ },
+ {
+ name: "bool true",
+ formats: []string{"text", "txt", "plain"},
+ value: true,
+ want: "true",
+ },
+ {
+ name: "bool false",
+ formats: []string{"text", "txt", "plain"},
+ value: false,
+ want: "false",
+ },
+ {
+ name: "implements fmt.Stringer",
+ formats: []string{"text", "txt", "plain"},
+ value: &mockStringer{value: "test string"},
+ want: "test string",
+ },
+ {
+ name: "error writing to writer with fmt.Stringer",
+ formats: []string{"text", "txt", "plain"},
writeErr: errors.New("write error!!1"),
value: &mockStringer{value: "test string"},
wantErr: "render: failed: write error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
- name: "text format with error from io.WriterTo",
- format: "text",
+ name: "implements io.WriterTo",
+ formats: []string{"text", "txt", "plain"},
+ value: &mockWriterTo{value: "test string"},
+ want: "test string",
+ },
+ {
+ name: "io.WriterTo error",
+ formats: []string{"text", "txt", "plain"},
value: &mockWriterTo{
value: "test string",
err: errors.New("WriteTo error!!1"),
},
wantErr: "render: failed: WriteTo error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
- name: "text format with invalid type",
- format: "text",
- value: make(chan int),
- wantErr: "render: unsupported format: text",
- wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
+ name: "implements io.Reader",
+ formats: []string{"text", "txt", "plain"},
+ valueFunc: func() any { return &mockReader{value: "reader string"} },
+ want: "reader string",
+ },
+ {
+ name: "io.Reader error",
+ formats: []string{"text", "txt", "plain"},
+ value: &mockReader{
+ value: "reader string",
+ err: errors.New("Read error!!1"),
+ },
+ wantErr: "render: failed: Read error!!1",
+ wantErrIs: []error{Err, ErrFailed},
+ },
+ {
+ name: "error",
+ formats: []string{"text", "txt", "plain"},
+ value: errors.New("this is an error"),
+ want: "this is an error",
+ },
+ {
+ name: "does not implement any supported type/interface",
+ formats: []string{"text", "txt", "plain"},
+ value: struct{}{},
+ wantErr: "render: unsupported format: {{format}}",
+ wantErrIs: []error{Err, ErrUnsupportedFormat},
},
}
// "xml" format.
var xmlFormatTestCases = []renderFormatTestCase{
{
- name: "xml format",
- format: "xml",
+ name: "xml format",
+ formats: []string{"xml"},
value: struct {
XMLName xml.Name `xml:"user"`
Age int `xml:"age"`
}{Age: 30},
- want: "\n 30\n",
+ wantPretty: "\n 30\n",
+ wantCompact: "30",
},
{
- name: "xml format with xml.Marshaler",
- format: "xml",
- value: &mockXMLMarshaler{elm: "test string"},
- want: "test string",
+ name: "xml format with xml.Marshaler",
+ formats: []string{"xml"},
+ value: &mockXMLMarshaler{elm: "test string"},
+ want: "test string",
},
{
name: "xml format with error from xml.Marshaler",
- format: "xml",
+ formats: []string{"xml"},
value: &mockXMLMarshaler{err: errors.New("marshal error!!1")},
wantErr: "render: failed: marshal error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "xml format with error writing to writer",
- format: "xml",
+ formats: []string{"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},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "xml format with invalid value",
- format: "xml",
+ formats: []string{"xml"},
value: make(chan int),
wantErr: "render: failed: xml: unsupported type: chan int",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, 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 map",
+ formats: []string{"yaml", "yml"},
+ value: map[string]int{"age": 30},
+ want: "age: 30\n",
},
{
- name: "yaml format with nested structure",
- format: "yaml",
+ name: "yaml format with nested structure",
+ formats: []string{"yaml", "yml"},
value: map[string]any{
"user": map[string]any{
"age": 30,
@@ -281,253 +391,148 @@ var yamlFormatTestCases = []renderFormatTestCase{
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 yaml.Marshaler",
+ formats: []string{"yaml", "yml"},
+ value: &mockYAMLMarshaler{val: map[string]int{"age": 30}},
+ want: "age: 30\n",
},
{
name: "yaml format with error from yaml.Marshaler",
- format: "yaml",
+ formats: []string{"yaml", "yml"},
value: &mockYAMLMarshaler{err: errors.New("mock error")},
wantErr: "render: failed: mock error",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "yaml format with error writing to writer",
- format: "yaml",
+ formats: []string{"yaml", "yml"},
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},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "yaml format with invalid type",
- format: "yaml",
+ formats: []string{"yaml", "yml"},
value: make(chan int),
wantPanic: "cannot marshal type: chan int",
},
}
-func TestRender(t *testing.T) {
+func TestPretty(t *testing.T) {
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}
+ for _, format := range tt.formats {
+ t.Run(format+" format "+tt.name, func(t *testing.T) {
+ w := &mockWriter{WriteErr: tt.writeErr}
- var err error
- var panicRes any
- func() {
- defer func() {
- if r := recover(); r != nil {
- panicRes = r
- }
+ value := tt.value
+ if tt.valueFunc != nil {
+ value = tt.valueFunc()
+ }
+
+ var err error
+ var panicRes any
+ func() {
+ defer func() {
+ if r := recover(); r != nil {
+ panicRes = r
+ }
+ }()
+ err = Pretty(w, format, value)
}()
- err = render.Render(w, tt.format, tt.value)
- }()
- got := w.String()
+ got := w.String()
+ var want string
+ if tt.wantPretty == "" && tt.wantCompact == "" {
+ want = tt.want
+ } else {
+ want = tt.wantPretty
+ }
- if tt.wantPanic != "" {
- assert.Equal(t, tt.wantPanic, panicRes)
- }
+ 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.wantErr != "" {
+ wantErr := strings.ReplaceAll(
+ tt.wantErr, "{{format}}", format,
+ )
+ assert.EqualError(t, err, 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)
- }
- })
+ if tt.wantPanic == "" &&
+ tt.wantErr == "" && len(tt.wantErrIs) == 0 {
+ assert.NoError(t, err)
+ assert.Equal(t, want, got)
+ }
+ })
+ }
}
}
-func TestNew(t *testing.T) {
- tests := []struct {
- name string
- formats []string
- want *render.Renderer
- wantErr string
- wantErrIs []error
- }{
- {
- name: "no formats",
- formats: []string{},
- wantErr: "render: no formats specified",
- wantErrIs: []error{render.Err},
- },
- {
- name: "single format",
- formats: []string{"json"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- },
- },
- },
- {
- name: "multiple formats",
- formats: []string{"json", "text", "yaml"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- "text": render.DefaultText,
- "yaml": render.DefaultYAML,
- },
- },
- },
- {
- name: "invalid format",
- formats: []string{"json", "text", "invalid"},
- wantErr: "render: unsupported format: invalid",
- wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
- },
- {
- name: "duplicate format",
- formats: []string{"json", "text", "json"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- "text": render.DefaultText,
- },
- },
- },
- {
- name: "all formats",
- formats: []string{"json", "text", "yaml", "xml", "binary"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- "text": render.DefaultText,
- "yaml": render.DefaultYAML,
- "xml": render.DefaultXML,
- "binary": render.DefaultBinary,
- },
- },
- },
- }
+func TestCompact(t *testing.T) {
+ 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) {
- got, err := render.New(tt.formats...)
+ for _, format := range tt.formats {
+ t.Run(format+" format "+tt.name, func(t *testing.T) {
+ w := &mockWriter{WriteErr: tt.writeErr}
- if tt.wantErr != "" {
- assert.EqualError(t, err, tt.wantErr)
- }
- for _, e := range tt.wantErrIs {
- assert.ErrorIs(t, err, e)
- }
+ value := tt.value
+ if tt.valueFunc != nil {
+ value = tt.valueFunc()
+ }
- if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
- assert.NoError(t, err)
- assert.Equal(t, tt.want, got)
- }
- })
- }
-}
-
-func TestMustNew(t *testing.T) {
- tests := []struct {
- name string
- formats []string
- want *render.Renderer
- wantErr string
- wantErrIs []error
- wantPanic string
- }{
- {
- name: "no formats",
- formats: []string{},
- wantPanic: "render: no formats specified",
- },
- {
- name: "single format",
- formats: []string{"json"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- },
- },
- },
- {
- name: "multiple formats",
- formats: []string{"json", "text", "yaml"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- "text": render.DefaultText,
- "yaml": render.DefaultYAML,
- },
- },
- },
- {
- name: "invalid format",
- formats: []string{"json", "text", "invalid"},
- wantPanic: "render: unsupported format: invalid",
- },
- {
- name: "duplicate format",
- formats: []string{"json", "text", "json"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- "text": render.DefaultText,
- },
- },
- },
- {
- name: "all formats",
- formats: []string{"json", "text", "yaml", "xml", "binary"},
- want: &render.Renderer{
- Formats: map[string]render.FormatRenderer{
- "json": render.DefaultJSON,
- "text": render.DefaultText,
- "yaml": render.DefaultYAML,
- "xml": render.DefaultXML,
- "binary": render.DefaultBinary,
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var got *render.Renderer
- var err error
- var panicRes any
- func() {
- defer func() {
- if r := recover(); r != nil {
- panicRes = r
- }
+ var err error
+ var panicRes any
+ func() {
+ defer func() {
+ if r := recover(); r != nil {
+ panicRes = r
+ }
+ }()
+ err = Compact(w, format, value)
}()
- got = render.MustNew(tt.formats...)
- }()
- if tt.wantPanic != "" {
- assert.Equal(t, tt.wantPanic, panicRes)
- }
+ got := w.String()
+ var want string
+ if tt.wantPretty == "" && tt.wantCompact == "" {
+ want = tt.want
+ } else {
+ want = tt.wantCompact
+ }
- if tt.wantErr != "" {
- assert.EqualError(t, err, tt.wantErr)
- }
- for _, e := range tt.wantErrIs {
- assert.ErrorIs(t, err, e)
- }
+ if tt.wantPanic != "" {
+ assert.Equal(t, tt.wantPanic, panicRes)
+ }
- if tt.wantPanic == "" &&
- tt.wantErr == "" && len(tt.wantErrIs) == 0 {
- assert.NoError(t, err)
- assert.Equal(t, tt.want, got)
- }
- })
+ if tt.wantErr != "" {
+ wantErr := strings.ReplaceAll(
+ tt.wantErr, "{{format}}", format,
+ )
+ assert.EqualError(t, err, 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, want, got)
+ }
+ })
+ }
}
}
diff --git a/renderer.go b/renderer.go
index 0acf58a..73af5e4 100644
--- a/renderer.go
+++ b/renderer.go
@@ -13,15 +13,32 @@ 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,
+ // Renderers 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
+ Renderers 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}
+// New returns a new Renderer that delegates rendering to the specified
+// renderers.
+//
+// Any renderers which implement the Formats interface, will also be set as the
+// renderer for all format strings returned by Format() on the renderer.
+func New(renderers map[string]FormatRenderer) *Renderer {
+ newRenderers := make(map[string]FormatRenderer, len(renderers))
+
+ for format, r := range renderers {
+ newRenderers[format] = r
+
+ if x, ok := r.(Formats); ok {
+ for _, f := range x.Formats() {
+ if f != format {
+ newRenderers[f] = r
+ }
+ }
+ }
+ }
+
+ return &Renderer{Renderers: newRenderers}
}
// Render renders a value to an io.Writer using the specified format. If the
@@ -32,7 +49,7 @@ func NewFormatRenderer(formats map[string]FormatRenderer) *Renderer {
// 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]
+ renderer, ok := r.Renderers[format]
if !ok {
return fmt.Errorf("%w: %s", ErrUnsupportedFormat, format)
}
@@ -51,3 +68,15 @@ func (r *Renderer) Render(w io.Writer, format string, v any) error {
return nil
}
+
+func (r *Renderer) OnlyWith(formats ...string) *Renderer {
+ renderers := make(map[string]FormatRenderer, len(formats))
+
+ for _, format := range formats {
+ if r, ok := r.Renderers[format]; ok {
+ renderers[format] = r
+ }
+ }
+
+ return New(renderers)
+}
diff --git a/renderer_test.go b/renderer_test.go
index e971892..1ab848f 100644
--- a/renderer_test.go
+++ b/renderer_test.go
@@ -1,12 +1,12 @@
-package render_test
+package render
import (
"bytes"
"errors"
"fmt"
+ "strings"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -14,7 +14,7 @@ import (
func TestRenderer_Render(t *testing.T) {
tests := []struct {
name string
- renderers map[string]render.FormatRenderer
+ renderers map[string]FormatRenderer
format string
value interface{}
want string
@@ -23,7 +23,7 @@ func TestRenderer_Render(t *testing.T) {
}{
{
name: "existing renderer",
- renderers: map[string]render.FormatRenderer{
+ renderers: map[string]FormatRenderer{
"mock": &mockRenderer{output: "mock output"},
},
format: "mock",
@@ -32,7 +32,7 @@ func TestRenderer_Render(t *testing.T) {
},
{
name: "existing renderer returns error",
- renderers: map[string]render.FormatRenderer{
+ renderers: map[string]FormatRenderer{
"other": &mockRenderer{
output: "mock output",
err: errors.New("mock error"),
@@ -44,30 +44,30 @@ func TestRenderer_Render(t *testing.T) {
},
{
name: "existing renderer returns ErrCannotRender",
- renderers: map[string]render.FormatRenderer{
+ renderers: map[string]FormatRenderer{
"other": &mockRenderer{
output: "mock output",
- err: fmt.Errorf("%w: mock", render.ErrCannotRender),
+ err: fmt.Errorf("%w: mock", ErrCannotRender),
},
},
format: "other",
value: struct{}{},
wantErr: "render: unsupported format: other",
- wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
+ wantErrIs: []error{Err, ErrUnsupportedFormat},
},
{
name: "non-existing renderer",
- renderers: map[string]render.FormatRenderer{},
+ renderers: map[string]FormatRenderer{},
format: "unknown",
value: struct{}{},
wantErr: "render: unsupported format: unknown",
- wantErrIs: []error{render.Err, render.ErrUnsupportedFormat},
+ wantErrIs: []error{Err, ErrUnsupportedFormat},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- fr := &render.Renderer{
- Formats: tt.renderers,
+ fr := &Renderer{
+ Renderers: tt.renderers,
}
var buf bytes.Buffer
@@ -98,41 +98,57 @@ func TestRenderer_RenderAllFormats(t *testing.T) {
tests = append(tests, yamlFormatTestCases...)
for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- w := &mockWriter{WriteErr: tt.writeErr}
+ for _, format := range tt.formats {
+ t.Run(format+" format "+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)
+ value := tt.value
+ if tt.valueFunc != nil {
+ value = tt.valueFunc()
+ }
- func() {
- defer func() {
- if r := recover(); r != nil {
- panicRes = r
- }
+ var err error
+ var panicRes any
+ renderer := compactRenderer
+ require.NoError(t, err)
+
+ func() {
+ defer func() {
+ if r := recover(); r != nil {
+ panicRes = r
+ }
+ }()
+ err = renderer.Render(w, format, value)
}()
- err = renderer.Render(w, tt.format, tt.value)
- }()
- got := w.String()
+ got := w.String()
+ var want string
+ if tt.wantPretty == "" && tt.wantCompact == "" {
+ want = tt.want
+ } else {
+ want = tt.wantCompact
+ }
- if tt.wantPanic != "" {
- assert.Equal(t, tt.wantPanic, panicRes)
- }
+ 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.wantErr != "" {
+ wantErr := strings.ReplaceAll(
+ tt.wantErr, "{{format}}", format,
+ )
+ assert.EqualError(t, err, 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)
- }
- })
+ if tt.wantPanic == "" &&
+ tt.wantErr == "" && len(tt.wantErrIs) == 0 {
+ assert.NoError(t, err)
+ assert.Equal(t, want, got)
+ }
+ })
+ }
}
}
diff --git a/text.go b/text.go
index ca1da49..47f7934 100644
--- a/text.go
+++ b/text.go
@@ -57,3 +57,7 @@ func (t *Text) Render(w io.Writer, v any) error {
return nil
}
+
+func (t *Text) Formats() []string {
+ return []string{"text", "txt", "plain"}
+}
diff --git a/text_test.go b/text_test.go
index 063f59e..7ec9891 100644
--- a/text_test.go
+++ b/text_test.go
@@ -1,4 +1,4 @@
-package render_test
+package render
import (
"errors"
@@ -6,7 +6,6 @@ import (
"io"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
)
@@ -102,7 +101,7 @@ func TestText_Render(t *testing.T) {
name: "nil",
value: nil,
wantErr: "render: cannot render: ",
- wantErrIs: []error{render.Err, render.ErrCannotRender},
+ wantErrIs: []error{Err, ErrCannotRender},
},
{
name: "byte slice",
@@ -153,7 +152,7 @@ func TestText_Render(t *testing.T) {
writeErr: errors.New("write error!!1"),
value: &mockStringer{value: "test string"},
wantErr: "render: failed: write error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "implements io.WriterTo",
@@ -167,7 +166,7 @@ func TestText_Render(t *testing.T) {
err: errors.New("WriteTo error!!1"),
},
wantErr: "render: failed: WriteTo error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "implements io.Reader",
@@ -181,7 +180,7 @@ func TestText_Render(t *testing.T) {
err: errors.New("Read error!!1"),
},
wantErr: "render: failed: Read error!!1",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "error",
@@ -192,12 +191,12 @@ func TestText_Render(t *testing.T) {
name: "does not implement any supported type/interface",
value: struct{}{},
wantErr: "render: cannot render: struct {}",
- wantErrIs: []error{render.Err, render.ErrCannotRender},
+ wantErrIs: []error{Err, ErrCannotRender},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- s := &render.Text{}
+ s := &Text{}
w := &mockWriter{WriteErr: tt.writeErr}
err := s.Render(w, tt.value)
@@ -217,7 +216,3 @@ func TestText_Render(t *testing.T) {
})
}
}
-
-func ptr[T any](v T) *T {
- return &v
-}
diff --git a/xml.go b/xml.go
index 8fd8c07..ff10da3 100644
--- a/xml.go
+++ b/xml.go
@@ -43,3 +43,7 @@ func (x *XML) Render(w io.Writer, v any) error {
return nil
}
+
+func (x *XML) Formats() []string {
+ return []string{"xml"}
+}
diff --git a/xml_test.go b/xml_test.go
index 2e5b33f..2f449a8 100644
--- a/xml_test.go
+++ b/xml_test.go
@@ -1,4 +1,4 @@
-package render_test
+package render
import (
"bytes"
@@ -6,7 +6,6 @@ import (
"errors"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
)
@@ -90,20 +89,20 @@ func TestXML_Render(t *testing.T) {
name: "error from xml.Marshaler",
value: &mockXMLMarshaler{err: errors.New("mock error")},
wantErr: "render: failed: mock error",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "invalid value",
pretty: false,
value: make(chan int),
wantErr: "render: failed: xml: unsupported type: chan int",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- x := &render.XML{
+ x := &XML{
Pretty: tt.pretty,
Prefix: tt.prefix,
Indent: tt.indent,
diff --git a/yaml.go b/yaml.go
index 46b2f3b..5f43ac9 100644
--- a/yaml.go
+++ b/yaml.go
@@ -17,10 +17,10 @@ type YAML struct {
var _ FormatRenderer = (*YAML)(nil)
// Render marshals the given value to YAML.
-func (j *YAML) Render(w io.Writer, v any) error {
+func (y *YAML) Render(w io.Writer, v any) error {
enc := yaml.NewEncoder(w)
- indent := j.Indent
+ indent := y.Indent
if indent == 0 {
indent = 2
}
@@ -34,3 +34,7 @@ func (j *YAML) Render(w io.Writer, v any) error {
return nil
}
+
+func (y *YAML) Formats() []string {
+ return []string{"yaml", "yml"}
+}
diff --git a/yaml_test.go b/yaml_test.go
index 27f2b38..477af86 100644
--- a/yaml_test.go
+++ b/yaml_test.go
@@ -1,11 +1,10 @@
-package render_test
+package render
import (
"bytes"
"errors"
"testing"
- "github.com/jimeh/go-render"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
@@ -67,7 +66,7 @@ func TestYAML_Render(t *testing.T) {
name: "error from yaml.Marshaler",
value: &mockYAMLMarshaler{err: errors.New("mock error")},
wantErr: "render: failed: mock error",
- wantErrIs: []error{render.Err, render.ErrFailed},
+ wantErrIs: []error{Err, ErrFailed},
},
{
name: "invalid value",
@@ -78,7 +77,7 @@ func TestYAML_Render(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- j := &render.YAML{
+ j := &YAML{
Indent: tt.indent,
}