mirror of
https://github.com/jimeh/go-render.git
synced 2026-02-19 03:16:39 +00:00
feat(deps): replace yaml.v3 with goccy/go-yaml library
Replace yaml.v3 with goccy/go-yaml, as the former is now unmaintained. Also upgrade minimum Go version to 1.21.0 and update testify to v1.10.0. Add support for configuring encoder options in YAML renderer to provide more flexibility in YAML output formatting. Include new options for sequence indentation and automatic integer conversion. Implement support for both yaml.InterfaceMarshaler and yaml.BytesMarshaler interfaces with appropriate test cases. Rename mock implementation to clarify interface implementation.
This commit is contained in:
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -58,7 +58,6 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go_version:
|
go_version:
|
||||||
- "1.20"
|
|
||||||
- "1.21"
|
- "1.21"
|
||||||
- "1.22"
|
- "1.22"
|
||||||
- "1.23"
|
- "1.23"
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -1,13 +1,14 @@
|
|||||||
module github.com/jimeh/go-render
|
module github.com/jimeh/go-render
|
||||||
|
|
||||||
go 1.20
|
go 1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/goccy/go-yaml v1.18.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
github.com/stretchr/testify v1.10.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -1,9 +1,11 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
115
render_test.go
115
render_test.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// mockWriter is a mock implementation of io.Writer.
|
||||||
type mockWriter struct {
|
type mockWriter struct {
|
||||||
WriteErr error
|
WriteErr error
|
||||||
buf bytes.Buffer
|
buf bytes.Buffer
|
||||||
@@ -30,6 +31,7 @@ func (mw *mockWriter) String() string {
|
|||||||
return mw.buf.String()
|
return mw.buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockHandler is a mock implementation of Handler.
|
||||||
type mockHandler struct {
|
type mockHandler struct {
|
||||||
output string
|
output string
|
||||||
formats []string
|
formats []string
|
||||||
@@ -55,6 +57,7 @@ func (mh *mockHandler) Formats() []string {
|
|||||||
return mh.formats
|
return mh.formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockPrettyHandler is a mock implementation of PrettyHandler.
|
||||||
type mockPrettyHandler struct {
|
type mockPrettyHandler struct {
|
||||||
output string
|
output string
|
||||||
prettyOutput string
|
prettyOutput string
|
||||||
@@ -92,6 +95,7 @@ func (mph *mockPrettyHandler) Formats() []string {
|
|||||||
return mph.formats
|
return mph.formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockFormatsHandler is a mock implementation of FormatsHandler.
|
||||||
type mockFormatsHandler struct {
|
type mockFormatsHandler struct {
|
||||||
output string
|
output string
|
||||||
formats []string
|
formats []string
|
||||||
@@ -128,7 +132,6 @@ type renderFormatTestCase struct {
|
|||||||
wantCompact string
|
wantCompact string
|
||||||
wantErr string
|
wantErr string
|
||||||
wantErrIs []error
|
wantErrIs []error
|
||||||
wantPanic string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "binary" format.
|
// "binary" format.
|
||||||
@@ -492,15 +495,38 @@ var yamlFormatTestCases = []renderFormatTestCase{
|
|||||||
want: "user:\n age: 30\n name: John Doe\n",
|
want: "user:\n age: 30\n name: John Doe\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "yaml format with yaml.Marshaler",
|
name: "yaml format with sequences",
|
||||||
|
value: map[string]any{
|
||||||
|
"books": []string{
|
||||||
|
"The Great Gatsby",
|
||||||
|
"1984",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with yaml.InterfaceMarshaler",
|
||||||
formats: []string{"yaml", "yml"},
|
formats: []string{"yaml", "yml"},
|
||||||
value: &mockYAMLMarshaler{val: map[string]int{"age": 30}},
|
value: &mockYAMLInterfaceMarshaler{val: map[string]int{"age": 30}},
|
||||||
want: "age: 30\n",
|
want: "age: 30\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "yaml format with error from yaml.Marshaler",
|
name: "yaml format with error from yaml.InterfaceMarshaler",
|
||||||
formats: []string{"yaml", "yml"},
|
formats: []string{"yaml", "yml"},
|
||||||
value: &mockYAMLMarshaler{err: errors.New("mock error")},
|
value: &mockYAMLInterfaceMarshaler{err: errors.New("mock error")},
|
||||||
|
wantErr: "render: failed: mock error",
|
||||||
|
wantErrIs: []error{Err, ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with yaml.BytesMarshaler",
|
||||||
|
formats: []string{"yaml", "yml"},
|
||||||
|
value: &mockYAMLBytesMarshaler{val: []byte("age: 30\n")},
|
||||||
|
want: "age: 30\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with error from yaml.BytesMarshaler",
|
||||||
|
formats: []string{"yaml", "yml"},
|
||||||
|
value: &mockYAMLBytesMarshaler{err: errors.New("mock error")},
|
||||||
wantErr: "render: failed: mock error",
|
wantErr: "render: failed: mock error",
|
||||||
wantErrIs: []error{Err, ErrFailed},
|
wantErrIs: []error{Err, ErrFailed},
|
||||||
},
|
},
|
||||||
@@ -516,7 +542,8 @@ var yamlFormatTestCases = []renderFormatTestCase{
|
|||||||
name: "yaml format with invalid type",
|
name: "yaml format with invalid type",
|
||||||
formats: []string{"yaml", "yml"},
|
formats: []string{"yaml", "yml"},
|
||||||
value: make(chan int),
|
value: make(chan int),
|
||||||
wantPanic: "cannot marshal type: chan int",
|
wantErr: "render: failed: unknown value type chan int",
|
||||||
|
wantErrIs: []error{Err, ErrFailed},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,18 +569,6 @@ func TestRender(t *testing.T) {
|
|||||||
value = tt.valueFunc()
|
value = tt.valueFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
var panicRes any
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicRes = r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = Render(w, format, pretty, value)
|
|
||||||
}()
|
|
||||||
|
|
||||||
got := w.String()
|
|
||||||
want := tt.want
|
want := tt.want
|
||||||
if pretty && tt.wantPretty != "" {
|
if pretty && tt.wantPretty != "" {
|
||||||
want = tt.wantPretty
|
want = tt.wantPretty
|
||||||
@@ -561,9 +576,8 @@ func TestRender(t *testing.T) {
|
|||||||
want = tt.wantCompact
|
want = tt.wantCompact
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic != "" {
|
err := Render(w, format, pretty, value)
|
||||||
assert.Equal(t, tt.wantPanic, panicRes)
|
got := w.String()
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
wantErr := strings.ReplaceAll(
|
wantErr := strings.ReplaceAll(
|
||||||
@@ -575,8 +589,7 @@ func TestRender(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, e)
|
assert.ErrorIs(t, err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic == "" &&
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
@@ -602,28 +615,15 @@ func TestPretty(t *testing.T) {
|
|||||||
value = tt.valueFunc()
|
value = tt.valueFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
want := tt.want
|
||||||
var panicRes any
|
if tt.wantPretty != "" {
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicRes = r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = Pretty(w, format, value)
|
|
||||||
}()
|
|
||||||
|
|
||||||
got := w.String()
|
|
||||||
var want string
|
|
||||||
if tt.wantPretty == "" && tt.wantCompact == "" {
|
|
||||||
want = tt.want
|
|
||||||
} else {
|
|
||||||
want = tt.wantPretty
|
want = tt.wantPretty
|
||||||
|
} else if tt.wantCompact != "" {
|
||||||
|
want = tt.wantCompact
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic != "" {
|
err := Pretty(w, format, value)
|
||||||
assert.Equal(t, tt.wantPanic, panicRes)
|
got := w.String()
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
wantErr := strings.ReplaceAll(
|
wantErr := strings.ReplaceAll(
|
||||||
@@ -635,8 +635,7 @@ func TestPretty(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, e)
|
assert.ErrorIs(t, err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic == "" &&
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
@@ -661,28 +660,15 @@ func TestCompact(t *testing.T) {
|
|||||||
value = tt.valueFunc()
|
value = tt.valueFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
want := tt.want
|
||||||
var panicRes any
|
if tt.wantPretty != "" {
|
||||||
func() {
|
want = tt.wantPretty
|
||||||
defer func() {
|
} else if tt.wantCompact != "" {
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicRes = r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = Compact(w, format, value)
|
|
||||||
}()
|
|
||||||
|
|
||||||
got := w.String()
|
|
||||||
var want string
|
|
||||||
if tt.wantPretty == "" && tt.wantCompact == "" {
|
|
||||||
want = tt.want
|
|
||||||
} else {
|
|
||||||
want = tt.wantCompact
|
want = tt.wantCompact
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic != "" {
|
err := Compact(w, format, value)
|
||||||
assert.Equal(t, tt.wantPanic, panicRes)
|
got := w.String()
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
wantErr := strings.ReplaceAll(
|
wantErr := strings.ReplaceAll(
|
||||||
@@ -694,8 +680,7 @@ func TestCompact(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, e)
|
assert.ErrorIs(t, err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic == "" &&
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,19 +493,6 @@ func TestRenderer_RenderAllFormats(t *testing.T) {
|
|||||||
value = tt.valueFunc()
|
value = tt.valueFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
var panicRes any
|
|
||||||
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicRes = r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = Base.Render(w, format, pretty, value)
|
|
||||||
}()
|
|
||||||
|
|
||||||
got := w.String()
|
|
||||||
want := tt.want
|
want := tt.want
|
||||||
if pretty && tt.wantPretty != "" {
|
if pretty && tt.wantPretty != "" {
|
||||||
want = tt.wantPretty
|
want = tt.wantPretty
|
||||||
@@ -513,9 +500,8 @@ func TestRenderer_RenderAllFormats(t *testing.T) {
|
|||||||
want = tt.wantCompact
|
want = tt.wantCompact
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic != "" {
|
err := Base.Render(w, format, pretty, value)
|
||||||
assert.Equal(t, tt.wantPanic, panicRes)
|
got := w.String()
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
wantErr := strings.ReplaceAll(
|
wantErr := strings.ReplaceAll(
|
||||||
@@ -527,8 +513,7 @@ func TestRenderer_RenderAllFormats(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, e)
|
assert.ErrorIs(t, err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic == "" &&
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
@@ -556,19 +541,6 @@ func TestRenderer_CompactAllFormats(t *testing.T) {
|
|||||||
value = tt.valueFunc()
|
value = tt.valueFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
var panicRes any
|
|
||||||
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicRes = r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = Base.Compact(w, format, value)
|
|
||||||
}()
|
|
||||||
|
|
||||||
got := w.String()
|
|
||||||
var want string
|
var want string
|
||||||
if tt.wantPretty == "" && tt.wantCompact == "" {
|
if tt.wantPretty == "" && tt.wantCompact == "" {
|
||||||
want = tt.want
|
want = tt.want
|
||||||
@@ -576,9 +548,8 @@ func TestRenderer_CompactAllFormats(t *testing.T) {
|
|||||||
want = tt.wantCompact
|
want = tt.wantCompact
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic != "" {
|
err := Base.Compact(w, format, value)
|
||||||
assert.Equal(t, tt.wantPanic, panicRes)
|
got := w.String()
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
wantErr := strings.ReplaceAll(
|
wantErr := strings.ReplaceAll(
|
||||||
@@ -590,8 +561,7 @@ func TestRenderer_CompactAllFormats(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, e)
|
assert.ErrorIs(t, err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic == "" &&
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
@@ -618,19 +588,6 @@ func TestRenderer_PrettyAllFormats(t *testing.T) {
|
|||||||
value = tt.valueFunc()
|
value = tt.valueFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
var panicRes any
|
|
||||||
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicRes = r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = Base.Pretty(w, format, value)
|
|
||||||
}()
|
|
||||||
|
|
||||||
got := w.String()
|
|
||||||
var want string
|
var want string
|
||||||
if tt.wantPretty == "" && tt.wantCompact == "" {
|
if tt.wantPretty == "" && tt.wantCompact == "" {
|
||||||
want = tt.want
|
want = tt.want
|
||||||
@@ -638,9 +595,8 @@ func TestRenderer_PrettyAllFormats(t *testing.T) {
|
|||||||
want = tt.wantPretty
|
want = tt.wantPretty
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic != "" {
|
err := Base.Pretty(w, format, value)
|
||||||
assert.Equal(t, tt.wantPanic, panicRes)
|
got := w.String()
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
wantErr := strings.ReplaceAll(
|
wantErr := strings.ReplaceAll(
|
||||||
@@ -652,8 +608,7 @@ func TestRenderer_PrettyAllFormats(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, e)
|
assert.ErrorIs(t, err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic == "" &&
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|||||||
54
yaml.go
54
yaml.go
@@ -4,16 +4,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"github.com/goccy/go-yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var YAMLDefaultIndent = 2
|
var (
|
||||||
|
// YAMLDefaultIndent is the default number of spaces to use for indenting
|
||||||
|
// nested blocks in the output YAML.
|
||||||
|
YAMLDefaultIndent = 2
|
||||||
|
|
||||||
|
// YAMLDefaultIndentSequence is the default value for the IndentSequence
|
||||||
|
// option passed to yaml.NewEncoder(). When true, sequences will be
|
||||||
|
// indented by default.
|
||||||
|
YAMLDefaultIndentSequence = true
|
||||||
|
)
|
||||||
|
|
||||||
// YAML is a Handler that marshals the given value to YAML.
|
// YAML is a Handler that marshals the given value to YAML.
|
||||||
type YAML struct {
|
type YAML struct {
|
||||||
// Indent controls how many spaces will be used for indenting nested blocks
|
// Indent is the number of spaces to use for indenting nested blocks in the
|
||||||
// in the output YAML. When Indent is zero, YAMLDefaultIndent will be used.
|
// output YAML. When empty/zero, YAMLDefaultIndent will be used.
|
||||||
Indent int
|
Indent int
|
||||||
|
|
||||||
|
// EncodeOptions is a list of options to pass to yaml.NewEncoder().
|
||||||
|
//
|
||||||
|
// These options will be appended to the default indent and sequence indent
|
||||||
|
// options. With duplicate options, the last one will take precedence.
|
||||||
|
EncodeOptions []yaml.EncodeOption
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -21,21 +36,38 @@ var (
|
|||||||
_ FormatsHandler = (*YAML)(nil)
|
_ FormatsHandler = (*YAML)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Render marshals the given value to YAML.
|
func (y *YAML) buildEncodeOptions() []yaml.EncodeOption {
|
||||||
func (y *YAML) Render(w io.Writer, v any) error {
|
indent := YAMLDefaultIndent
|
||||||
indent := y.Indent
|
if y.Indent > 0 {
|
||||||
if indent == 0 {
|
indent = y.Indent
|
||||||
indent = YAMLDefaultIndent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := yaml.NewEncoder(w)
|
opts := []yaml.EncodeOption{
|
||||||
enc.SetIndent(indent)
|
yaml.Indent(indent),
|
||||||
|
yaml.IndentSequence(YAMLDefaultIndentSequence),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(y.EncodeOptions) > 0 {
|
||||||
|
opts = append(opts, y.EncodeOptions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render marshals the given value to YAML.
|
||||||
|
func (y *YAML) Render(w io.Writer, v any) error {
|
||||||
|
enc := yaml.NewEncoder(w, y.buildEncodeOptions()...)
|
||||||
|
|
||||||
err := enc.Encode(v)
|
err := enc.Encode(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrFailed, err)
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = enc.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", ErrFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
340
yaml_test.go
340
yaml_test.go
@@ -1,43 +1,58 @@
|
|||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockYAMLMarshaler struct {
|
func ptr[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockYAMLInterfaceMarshaler struct {
|
||||||
val any
|
val any
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ yaml.Marshaler = (*mockYAMLMarshaler)(nil)
|
var _ yaml.InterfaceMarshaler = (*mockYAMLInterfaceMarshaler)(nil)
|
||||||
|
|
||||||
func (m *mockYAMLMarshaler) MarshalYAML() (any, error) {
|
func (m *mockYAMLInterfaceMarshaler) MarshalYAML() (any, error) {
|
||||||
|
return m.val, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockYAMLBytesMarshaler struct {
|
||||||
|
val []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ yaml.BytesMarshaler = (*mockYAMLBytesMarshaler)(nil)
|
||||||
|
|
||||||
|
func (m *mockYAMLBytesMarshaler) MarshalYAML() ([]byte, error) {
|
||||||
return m.val, m.err
|
return m.val, m.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestYAML_Render(t *testing.T) {
|
func TestYAML_Render(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
indent int
|
encoderOptions []yaml.EncodeOption
|
||||||
value any
|
indent int
|
||||||
want string
|
value any
|
||||||
wantErr string
|
want string
|
||||||
wantErrIs []error
|
writeErr error
|
||||||
wantPanic string
|
wantErr string
|
||||||
|
wantErrIs []error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "simple object default indent",
|
name: "simple object",
|
||||||
value: map[string]int{"age": 30},
|
value: map[string]int{"age": 30},
|
||||||
want: "age: 30\n",
|
want: "age: 30\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nested structure",
|
name: "nested object",
|
||||||
indent: 0, // This will use the default indent of 2 spaces
|
|
||||||
value: map[string]any{
|
value: map[string]any{
|
||||||
"user": map[string]any{
|
"user": map[string]any{
|
||||||
"age": 30,
|
"age": 30,
|
||||||
@@ -47,7 +62,44 @@ func TestYAML_Render(t *testing.T) {
|
|||||||
want: "user:\n age: 30\n name: John Doe\n",
|
want: "user:\n age: 30\n name: John Doe\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "simple object custom indent",
|
name: "sequences",
|
||||||
|
value: map[string]any{
|
||||||
|
"items": map[string]any{
|
||||||
|
"books": []string{
|
||||||
|
"The Great Gatsby",
|
||||||
|
"1984",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "items:\n books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sequences without IndentSequence",
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.IndentSequence(false),
|
||||||
|
},
|
||||||
|
value: map[string]any{
|
||||||
|
"items": map[string]any{
|
||||||
|
"books": []string{
|
||||||
|
"The Great Gatsby",
|
||||||
|
"1984",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "items:\n books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom AutoInt encoder option",
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.AutoInt(),
|
||||||
|
},
|
||||||
|
value: map[string]any{
|
||||||
|
"age": 1.0,
|
||||||
|
},
|
||||||
|
want: "age: 1\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested object with custom indent",
|
||||||
indent: 4,
|
indent: 4,
|
||||||
value: map[string]any{
|
value: map[string]any{
|
||||||
"user": map[string]any{
|
"user": map[string]any{
|
||||||
@@ -58,46 +110,86 @@ func TestYAML_Render(t *testing.T) {
|
|||||||
want: "user:\n age: 30\n name: John Doe\n",
|
want: "user:\n age: 30\n name: John Doe\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "implements yaml.Marshaler",
|
name: "sequences with custom indent",
|
||||||
value: &mockYAMLMarshaler{val: map[string]int{"age": 30}},
|
indent: 4,
|
||||||
|
value: map[string]any{
|
||||||
|
"items": map[string]any{
|
||||||
|
"books": []string{
|
||||||
|
"The Great Gatsby",
|
||||||
|
"1984",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sequences with custom indent and without IndentSequence",
|
||||||
|
indent: 4,
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.IndentSequence(false),
|
||||||
|
},
|
||||||
|
value: map[string]any{
|
||||||
|
"items": map[string]any{
|
||||||
|
"books": []string{
|
||||||
|
"The Great Gatsby",
|
||||||
|
"1984",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "implements yaml.InterfaceMarshaler",
|
||||||
|
value: &mockYAMLInterfaceMarshaler{val: map[string]int{"age": 30}},
|
||||||
want: "age: 30\n",
|
want: "age: 30\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error from yaml.Marshaler",
|
name: "error from yaml.InterfaceMarshaler",
|
||||||
value: &mockYAMLMarshaler{err: errors.New("mock error")},
|
value: &mockYAMLInterfaceMarshaler{
|
||||||
|
err: errors.New("mock error"),
|
||||||
|
},
|
||||||
wantErr: "render: failed: mock error",
|
wantErr: "render: failed: mock error",
|
||||||
wantErrIs: []error{Err, ErrFailed},
|
wantErrIs: []error{Err, ErrFailed},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "implements yaml.BytesMarshaler",
|
||||||
|
value: &mockYAMLBytesMarshaler{val: []byte("age: 30\n")},
|
||||||
|
want: "age: 30\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error from yaml.BytesMarshaler",
|
||||||
|
value: &mockYAMLBytesMarshaler{err: errors.New("mock error")},
|
||||||
|
wantErr: "render: failed: mock error",
|
||||||
|
wantErrIs: []error{Err, ErrFailed},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml format with error writing to writer",
|
||||||
|
writeErr: errors.New("write error!!1"),
|
||||||
|
value: map[string]int{"age": 30},
|
||||||
|
wantErr: "render: failed: yaml: write error: write error!!1",
|
||||||
|
wantErrIs: []error{Err, ErrFailed},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "invalid value",
|
name: "invalid value",
|
||||||
indent: 0,
|
|
||||||
value: make(chan int),
|
value: make(chan int),
|
||||||
wantPanic: "cannot marshal type: chan int",
|
wantErr: "render: failed: unknown value type chan int",
|
||||||
|
wantErrIs: []error{Err, ErrFailed},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
j := &YAML{
|
j := &YAML{
|
||||||
Indent: tt.indent,
|
Indent: tt.indent,
|
||||||
|
EncodeOptions: tt.encoderOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
w := &mockWriter{WriteErr: tt.writeErr}
|
||||||
var err error
|
|
||||||
var panicRes any
|
|
||||||
func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
panicRes = r
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = j.Render(&buf, tt.value)
|
|
||||||
}()
|
|
||||||
|
|
||||||
got := buf.String()
|
err := j.Render(w, tt.value)
|
||||||
|
got := w.String()
|
||||||
|
|
||||||
if tt.wantPanic != "" {
|
|
||||||
assert.Equal(t, tt.wantPanic, panicRes)
|
|
||||||
}
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
assert.EqualError(t, err, tt.wantErr)
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
}
|
}
|
||||||
@@ -105,8 +197,7 @@ func TestYAML_Render(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, e)
|
assert.ErrorIs(t, err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.wantPanic == "" &&
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
||||||
tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, got)
|
assert.Equal(t, tt.want, got)
|
||||||
}
|
}
|
||||||
@@ -119,3 +210,172 @@ func TestYAML_Formats(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, []string{"yaml", "yml"}, h.Formats())
|
assert.Equal(t, []string{"yaml", "yml"}, h.Formats())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestYAML_DefaultIndent(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
defaultVal *int
|
||||||
|
indent int
|
||||||
|
encoderOptions []yaml.EncodeOption
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "do not set default, indent, or encoder options",
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom default takes precedence over default",
|
||||||
|
defaultVal: ptr(4),
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "indent takes precedence over default",
|
||||||
|
indent: 4,
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "encoder option takes precedence over default",
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.Indent(4),
|
||||||
|
},
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "indent takes precedence over custom default",
|
||||||
|
defaultVal: ptr(4),
|
||||||
|
indent: 3,
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "encoder option takes precedence over indent",
|
||||||
|
indent: 4,
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.Indent(3),
|
||||||
|
},
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "encoder option takes precedence over indent and " +
|
||||||
|
"custom default",
|
||||||
|
defaultVal: ptr(5),
|
||||||
|
indent: 4,
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.Indent(3),
|
||||||
|
},
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Capture and restore the original default value.
|
||||||
|
originalDefault := YAMLDefaultIndent
|
||||||
|
t.Cleanup(func() { YAMLDefaultIndent = originalDefault })
|
||||||
|
|
||||||
|
if tt.defaultVal != nil {
|
||||||
|
YAMLDefaultIndent = *tt.defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
y := &YAML{
|
||||||
|
Indent: tt.indent,
|
||||||
|
EncodeOptions: tt.encoderOptions,
|
||||||
|
}
|
||||||
|
w := &mockWriter{}
|
||||||
|
|
||||||
|
value := map[string]any{
|
||||||
|
"items": map[string]any{
|
||||||
|
"books": []string{
|
||||||
|
"The Great Gatsby",
|
||||||
|
"1984",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := y.Render(w, value)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, w.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAML_DefaultIndentSequence(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
defaultVal *bool
|
||||||
|
encoderOptions []yaml.EncodeOption
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "do not set default or encoder options",
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "set default to true",
|
||||||
|
defaultVal: ptr(true),
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "set default to true and encoder option to false",
|
||||||
|
defaultVal: ptr(true),
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.IndentSequence(false),
|
||||||
|
},
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "set default to false",
|
||||||
|
defaultVal: ptr(false),
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "set default to false and encoder option to true",
|
||||||
|
defaultVal: ptr(false),
|
||||||
|
encoderOptions: []yaml.EncodeOption{
|
||||||
|
yaml.IndentSequence(true),
|
||||||
|
},
|
||||||
|
want: "items:\n " +
|
||||||
|
"books:\n - The Great Gatsby\n - \"1984\"\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Capture and restore the original default value.
|
||||||
|
originalDefault := YAMLDefaultIndentSequence
|
||||||
|
t.Cleanup(func() { YAMLDefaultIndentSequence = originalDefault })
|
||||||
|
|
||||||
|
if tt.defaultVal != nil {
|
||||||
|
YAMLDefaultIndentSequence = *tt.defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
y := &YAML{
|
||||||
|
EncodeOptions: tt.encoderOptions,
|
||||||
|
}
|
||||||
|
w := &mockWriter{}
|
||||||
|
|
||||||
|
value := map[string]any{
|
||||||
|
"items": map[string]any{
|
||||||
|
"books": []string{
|
||||||
|
"The Great Gatsby",
|
||||||
|
"1984",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := y.Render(w, value)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, w.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user