diff --git a/binary.go b/binary.go index 436c760..d27c6ec 100644 --- a/binary.go +++ b/binary.go @@ -17,7 +17,7 @@ var _ FormatRenderer = (*Binary)(nil) func (bm *Binary) Render(w io.Writer, v any) error { x, ok := v.(encoding.BinaryMarshaler) if !ok { - return ErrCannotRender + return fmt.Errorf("%w: %T", ErrCannotRender, v) } b, err := x.MarshalBinary() diff --git a/binary_test.go b/binary_test.go index 7a7d749..f8e0cf2 100644 --- a/binary_test.go +++ b/binary_test.go @@ -37,6 +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}, }, { diff --git a/multi.go b/multi.go index 1b01201..4014176 100644 --- a/multi.go +++ b/multi.go @@ -2,6 +2,7 @@ package render import ( "errors" + "fmt" "io" ) @@ -26,5 +27,5 @@ func (mr *MultiRenderer) Render(w io.Writer, v any) error { } } - return ErrCannotRender + return fmt.Errorf("%w: %T", ErrCannotRender, v) } diff --git a/multi_test.go b/multi_test.go index 3f533d3..268a80c 100644 --- a/multi_test.go +++ b/multi_test.go @@ -28,7 +28,8 @@ func TestMultiRenderer_Render(t *testing.T) { cannotRenderer, cannotRenderer, }, - value: struct{}{}, + value: "test", + wantErr: "render: cannot render: string", wantErrIs: []error{render.ErrCannotRender}, }, { diff --git a/render.go b/render.go index cda00da..f234ce5 100644 --- a/render.go +++ b/render.go @@ -104,6 +104,10 @@ func Render(w io.Writer, format string, v any) error { func New(formats ...string) (*Renderer, error) { renderers := map[string]FormatRenderer{} + if len(formats) == 0 { + return nil, fmt.Errorf("%w: no formats specified", Err) + } + for _, format := range formats { switch format { case "binary": @@ -128,7 +132,7 @@ func New(formats ...string) (*Renderer, error) { func MustNew(formats ...string) *Renderer { r, err := New(formats...) if err != nil { - panic(err) + panic(err.Error()) } return r diff --git a/render_example_test.go b/render_example_test.go index 64f7d37..ba25c4d 100644 --- a/render_example_test.go +++ b/render_example_test.go @@ -201,7 +201,77 @@ func (u *User) String() string { ) } -func ExampleRender_textViaStringer() { +func ExampleRender_textFromByteSlice() { + data := []byte("Hello, World!1") + + // Render the object to XML. + buf := &bytes.Buffer{} + err := render.Render(buf, "text", data) + if err != nil { + panic(err) + } + fmt.Println(buf.String()) + + // Output: + // Hello, World!1 +} + +func ExampleRender_textFromString() { + data := "Hello, World!" + + // Render the object to XML. + buf := &bytes.Buffer{} + err := render.Render(buf, "text", data) + if err != nil { + panic(err) + } + fmt.Println(buf.String()) + + // Output: + // Hello, World! +} + +func ExampleRender_textFromIOReader() { + var data io.Reader = strings.NewReader("Hello, World!!!1") + + // Render the object to XML. + buf := &bytes.Buffer{} + err := render.Render(buf, "text", data) + if err != nil { + panic(err) + } + fmt.Println(buf.String()) + + // Output: + // Hello, World!!!1 +} + +func ExampleRender_textFromWriterTo() { + // The Role struct has a WriteTo method which writes a string representation + // of a role to an io.Writer: + // + // func (r *Role) WriteTo(w io.Writer) (int64, error) { + // s := fmt.Sprintf("%s (%s)", r.Name, r.Icon) + // n, err := w.Write([]byte(s)) + // + // return int64(n), err + // } + + data := &Role{Name: "admin", Icon: "shield"} + + // Render the object to XML. + buf := &bytes.Buffer{} + err := render.Render(buf, "text", data) + if err != nil { + panic(err) + } + fmt.Println(buf.String()) + + // Output: + // admin (shield) +} + +func ExampleRender_textFromStringer() { // The User struct has a String method which returns a string representation // of a user: // @@ -233,28 +303,3 @@ func ExampleRender_textViaStringer() { // Output: // John Doe (30): golang, json, yaml, toml } - -func ExampleRender_textViaWriterTo() { - // The Role struct has a WriteTo method which writes a string representation - // of a role to an io.Writer: - // - // func (r *Role) WriteTo(w io.Writer) (int64, error) { - // s := fmt.Sprintf("%s (%s)", r.Name, r.Icon) - // n, err := w.Write([]byte(s)) - // - // return int64(n), err - // } - - data := &Role{Name: "admin", Icon: "shield"} - - // Render the object to XML. - buf := &bytes.Buffer{} - err := render.Render(buf, "text", data) - if err != nil { - panic(err) - } - fmt.Println(buf.String()) - - // Output: - // admin (shield) -} diff --git a/render_test.go b/render_test.go index de055e2..f9002ae 100644 --- a/render_test.go +++ b/render_test.go @@ -351,3 +351,183 @@ func TestRender(t *testing.T) { }) } } + +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, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := render.New(tt.formats...) + + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } + for _, e := range tt.wantErrIs { + assert.ErrorIs(t, err, e) + } + + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} + +func 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 + } + }() + got = render.MustNew(tt.formats...) + }() + + if tt.wantPanic != "" { + assert.Equal(t, tt.wantPanic, panicRes) + } + + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } + for _, e := range tt.wantErrIs { + assert.ErrorIs(t, err, e) + } + + if tt.wantPanic == "" && + tt.wantErr == "" && len(tt.wantErrIs) == 0 { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +}