From de3a9e55a82d624bfa25ad0735d3e8735cac646c Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sun, 24 Mar 2024 03:28:08 +0000 Subject: [PATCH] refactor: yet another one, someday I might be happy --- .golangci.yml | 2 + binary.go | 6 +- binary_test.go | 11 +- json.go | 12 +- json_test.go | 9 +- multi.go | 27 +- multi_test.go | 25 +- render.go | 151 ++++++---- render_example_test.go | 16 +- render_test.go | 661 +++++++++++++++++++++-------------------- renderer.go | 43 ++- renderer_test.go | 98 +++--- text.go | 4 + text_test.go | 19 +- xml.go | 4 + xml_test.go | 9 +- yaml.go | 8 +- yaml_test.go | 7 +- 18 files changed, 613 insertions(+), 499 deletions(-) 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, }