4 Commits

Author SHA1 Message Date
pugnack
b42eafe69f add unit-tests (#136) 2025-05-13 23:50:09 +03:00
0f33dbcfaf add options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2025-05-13 19:51:26 +03:00
43b5b349e6 allow to replace xml version 1.1 to 1.0 on the fly
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2025-05-13 18:23:37 +03:00
efb75b8357 add codec.RawMessage support
All checks were successful
test / test (push) Successful in 3m25s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-23 20:57:22 +03:00
5 changed files with 216 additions and 1555 deletions

15
go.mod
View File

@@ -1,10 +1,19 @@
module go.unistack.org/micro-codec-xml/v3 module go.unistack.org/micro-codec-xml/v3
go 1.16 go 1.21
toolchain go1.23.4
require ( require (
github.com/imdario/mergo v0.3.13 // indirect github.com/stretchr/testify v1.10.0
go.unistack.org/micro-proto/v3 v3.4.1 go.unistack.org/micro-proto/v3 v3.4.1
go.unistack.org/micro/v3 v3.10.88 go.unistack.org/micro/v3 v3.10.88
google.golang.org/protobuf v1.34.2 // indirect )
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
google.golang.org/protobuf v1.35.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

1561
go.sum

File diff suppressed because it is too large Load Diff

40
reader_test.go Normal file
View File

@@ -0,0 +1,40 @@
package xml
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestReader_Read(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "with replacement",
input: []byte(`<?xml version="1.1" encoding="UTF-8"?><root name="NAME"><item>ITEM</item></root>`),
expected: []byte(`<?xml version="1.0" encoding="UTF-8"?><root name="NAME"><item>ITEM</item></root>`),
},
{
name: "without replacement",
input: []byte(`<?xml version="1.0" encoding="UTF-8"?><root name="NAME"><item>ITEM</item></root>`),
expected: []byte(`<?xml version="1.0" encoding="UTF-8"?><root name="NAME"><item>ITEM</item></root>`),
},
{
name: "check invalid replacement",
input: []byte(`<?xml version="1.0" encoding="UTF-8"?><root version="1.1"><item>ITEM</item></root>`),
expected: []byte(`<?xml version="1.0" encoding="UTF-8"?><root version="1.1"><item>ITEM</item></root>`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := make([]byte, len(tt.input))
n, err := newReader(tt.input).Read(buf)
require.NoError(t, err)
require.Equal(t, tt.expected, buf[:n])
})
}
}

85
xml.go
View File

@@ -2,7 +2,9 @@
package xml // import "go.unistack.org/micro-codec-xml/v3" package xml // import "go.unistack.org/micro-codec-xml/v3"
import ( import (
"bytes"
"encoding/xml" "encoding/xml"
"io"
pb "go.unistack.org/micro-proto/v3/codec" pb "go.unistack.org/micro-proto/v3/codec"
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
@@ -19,6 +21,18 @@ const (
flattenTag = "flatten" flattenTag = "flatten"
) )
type unmarshalStrictKey struct{}
func UnmarshalStrict(b bool) codec.Option {
return codec.SetOption(unmarshalStrictKey{}, b)
}
type unmarshalXML11Key struct{}
func UnmarshalXML11(b bool) codec.Option {
return codec.SetOption(unmarshalXML11Key{}, b)
}
func (c *xmlCodec) Marshal(v interface{}, opts ...codec.Option) ([]byte, error) { func (c *xmlCodec) Marshal(v interface{}, opts ...codec.Option) ([]byte, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil
@@ -40,6 +54,10 @@ func (c *xmlCodec) Marshal(v interface{}, opts ...codec.Option) ([]byte, error)
return m.Data, nil return m.Data, nil
case *pb.Frame: case *pb.Frame:
return m.Data, nil return m.Data, nil
case codec.RawMessage:
return []byte(m), nil
case *codec.RawMessage:
return []byte(*m), nil
} }
return xml.Marshal(v) return xml.Marshal(v)
@@ -68,9 +86,74 @@ func (c *xmlCodec) Unmarshal(b []byte, v interface{}, opts ...codec.Option) erro
case *pb.Frame: case *pb.Frame:
m.Data = b m.Data = b
return nil return nil
case *codec.RawMessage:
*m = append((*m)[0:0], b...)
return nil
case codec.RawMessage:
copy(m, b)
return nil
} }
return xml.Unmarshal(b, v) var unmarshalStrict bool
var unmarshalXML11 bool
if options.Context != nil {
if v, ok := options.Context.Value(unmarshalStrictKey{}).(bool); ok {
unmarshalStrict = v
}
if v, ok := options.Context.Value(unmarshalXML11Key{}).(bool); ok {
unmarshalXML11 = v
}
}
var r io.Reader
if unmarshalXML11 {
r = newReader(b)
} else {
r = bytes.NewReader(b)
}
d := xml.NewDecoder(r)
if unmarshalStrict {
d.Strict = true
}
return d.Decode(v)
}
type reader struct {
s []byte
i int64 // current reading index
prevRune int // index of previous rune; or < 0
skip bool
}
func newReader(b []byte) io.Reader {
return &reader{b, 0, -1, false}
}
var (
srcP = []byte(`<?xml version="1.1"`)
srcL = len(srcP)
dstP = []byte(`<?xml version="1.0"`)
)
func (r *reader) Read(b []byte) (n int, err error) {
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
r.prevRune = -1
n = copy(b, r.s[r.i:])
if !r.skip {
if idx := bytes.Index(b, srcP); idx >= 0 {
copy(b[idx:idx+srcL], dstP)
r.skip = true
}
}
r.i += int64(n)
return
} }
func (c *xmlCodec) String() string { func (c *xmlCodec) String() string {

70
xml_test.go Normal file
View File

@@ -0,0 +1,70 @@
package xml
import (
"encoding/xml"
"testing"
"github.com/stretchr/testify/require"
"go.unistack.org/micro/v3/codec"
)
func TestXmlCodec_Unmarshal(t *testing.T) {
type result struct {
XMLName xml.Name `xml:"root"`
Name string `xml:"name,attr"`
Item string `xml:"item"`
}
tests := []struct {
name string
input []byte
isXML11 bool
expected result
}{
{
name: "without version",
input: []byte(`<root name="NAME"><item>ITEM</item></root>`),
expected: result{
XMLName: xml.Name{Local: "root"},
Name: "NAME",
Item: "ITEM",
},
},
{
name: "version 1.0",
input: []byte(`<?xml version="1.0" encoding="UTF-8"?><root name="NAME"><item>ITEM</item></root>`),
expected: result{
XMLName: xml.Name{Local: "root"},
Name: "NAME",
Item: "ITEM",
},
},
{
name: "version 1.1",
input: []byte(`<?xml version="1.1" encoding="UTF-8"?><root name="NAME"><item>ITEM</item></root>`),
isXML11: true,
expected: result{
XMLName: xml.Name{Local: "root"},
Name: "NAME",
Item: "ITEM",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var (
r result
opts []codec.Option
)
if tt.isXML11 {
opts = append(opts, UnmarshalXML11(true))
}
err := NewCodec(opts...).Unmarshal(tt.input, &r)
require.NoError(t, err)
require.Equal(t, tt.expected, r)
})
}
}