Skip to content

Commit

Permalink
Merge pull request #49 from rsocket/bugfix/composite_metadata_compati…
Browse files Browse the repository at this point in the history
…bility

Add new implementation of CompositeMetadata.
  • Loading branch information
jjeffcaii authored May 2, 2020
2 parents ff0cba6 + f0969c5 commit de93b5c
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 118 deletions.
183 changes: 97 additions & 86 deletions extension/composite_metadata.go
Original file line number Diff line number Diff line change
@@ -1,128 +1,139 @@
package extension

import (
"bytes"
"fmt"
"io"
"math"

"github.com/rsocket/rsocket-go/internal/common"
)

// CompositeMetadata provides multi Metadata payloads with different MIME types.
type CompositeMetadata interface {
io.WriterTo
// MIME returns MIME type.
MIME() string
// Payload returns bytes of Metadata payload.
Payload() []byte

encode() ([]byte, error)
type CompositeMetadata []byte

// CompositeMetadataScanner can be used to scan entry in CompositeMetadata.
type CompositeMetadataScanner struct {
offset int
raw []byte
}

// DecodeCompositeMetadata decode bytes to composite metadata.
func DecodeCompositeMetadata(raw []byte) ([]CompositeMetadata, error) {
ret := make([]CompositeMetadata, 0)
offset := 0
for offset < len(raw) {
l, cm, err := decodeCompositeMetadataOnce(raw[offset:])
if err != nil {
return nil, err
}
ret = append(ret, cm)
offset += l
// CompositeMetadataBuilder can be used to build a CompositeMetadata.
type CompositeMetadataBuilder struct {
k []interface{}
v [][]byte
}

// Scanner returns a entry scanner.
func (c CompositeMetadata) Scanner() *CompositeMetadataScanner {
return &CompositeMetadataScanner{
offset: 0,
raw: c,
}
return ret, nil
}

// NewCompositeMetadata returns a new composite metadata.
func NewCompositeMetadata(mime string, metadataPayload []byte) CompositeMetadata {
if found, ok := mimeTypesR[mime]; ok {
return &implCompositeMetadata{
mime: found,
data: metadataPayload,
}
// Scan returns true when scanner has more content.
func (c *CompositeMetadataScanner) Scan() bool {
return c.offset < len(c.raw)
}

// MetadataUTF8 returns current metadata in utf8 string.
func (c *CompositeMetadataScanner) MetadataUTF8() (mimeType string, metadata string, err error) {
mimeType, metadataRaw, err := c.Metadata()
if err != nil {
return
}
return &implCompositeMetadata{
mime: mime,
data: metadataPayload,
metadata = string(metadataRaw)
return
}

// MetadataUTF8 returns current metadata bytes.
func (c *CompositeMetadataScanner) Metadata() (mimeType string, metadata []byte, err error) {
l, mimeType, metadata, err := c.decodeCompositeMetadataOnce(c.raw[c.offset:])
if err != nil {
return
}
c.offset += l
return
}

func decodeCompositeMetadataOnce(raw []byte) (l int, cm CompositeMetadata, err error) {
func (c *CompositeMetadataScanner) decodeCompositeMetadataOnce(raw []byte) (length int, mimeType string, metadata []byte, err error) {
m := raw[0]
size := 1
idOrLen := (m << 1) >> 1
ret := &implCompositeMetadata{}
if m&0x80 == 0x80 {
ret.mime = MIME(idOrLen)
mimeType = MIME(idOrLen).String()
} else {
size += int(idOrLen)
ret.mime = string(raw[1 : 1+idOrLen])
mimeTypeLen := int(idOrLen) + 1
size += mimeTypeLen
mimeType = string(raw[1 : 1+mimeTypeLen])
}
metadataLen := common.NewUint24Bytes(raw[size : size+3]).AsInt()
end := size + 3 + metadataLen
ret.data = raw[size+3 : end]
return end, ret, nil
length = size + 3 + metadataLen
metadata = raw[size+3 : length]
return
}

type implCompositeMetadata struct {
mime interface{}
data []byte
// PushWellKnown push a WellKnownMimeType and metadata bytes.
func (c *CompositeMetadataBuilder) PushWellKnown(mimeType MIME, metadata []byte) *CompositeMetadataBuilder {
c.k = append(c.k, mimeType)
c.v = append(c.v, metadata)
return c
}

func (p *implCompositeMetadata) WriteTo(w io.Writer) (n int64, err error) {
var bs []byte
bs, err = p.encode()
if err != nil {
return
}
var wrote int
wrote, err = w.Write(bs)
if err != nil {
return
// PushWellKnownString push a WellKnownMimeType and metadata string.
func (c *CompositeMetadataBuilder) PushWellKnownString(mimeType MIME, metadata string) *CompositeMetadataBuilder {
return c.PushWellKnown(mimeType, []byte(metadata))
}

// Push push a custom MimeType and metadata bytes.
func (c *CompositeMetadataBuilder) Push(mimeType string, metadata []byte) *CompositeMetadataBuilder {
if well, ok := ParseMIME(mimeType); ok {
c.k = append(c.k, well)
} else {
c.k = append(c.k, mimeType)
}
n = int64(wrote)
return
c.v = append(c.v, metadata)
return c
}

func (p *implCompositeMetadata) String() string {
return fmt.Sprintf("CompositeMetadata{MIME=%s,payload=%s}", p.MIME(), p.Payload())
// PushString push a custom MimeType and metadata string.
func (c *CompositeMetadataBuilder) PushString(mimeType string, metadata string) *CompositeMetadataBuilder {
return c.Push(mimeType, []byte(metadata))
}

func (p *implCompositeMetadata) MIME() string {
switch mime := p.mime.(type) {
case MIME:
found, ok := mimeTypes[mime]
if !ok {
panic(fmt.Errorf("invalid MIME ID: %d", mime))
// Build build a new CompositeMetadata.
func (c *CompositeMetadataBuilder) Build() (CompositeMetadata, error) {
bf := bytes.Buffer{}
for i := 0; i < len(c.k); i++ {
switch mimeType := c.k[i].(type) {
case MIME:
bf.WriteByte(0x80 | byte(mimeType))
case string:
mimeTypeLen := len(mimeType)
if mimeTypeLen > math.MaxInt8 {
return nil, fmt.Errorf("length of MIME type is over %d", math.MaxInt8)
}
bf.WriteByte(byte(mimeTypeLen - 1))
bf.Write([]byte(mimeType))
default:
panic("unreachable")
}
metadata := c.v[i]
metadataLen := len(metadata)
bf.Write(common.NewUint24(metadataLen).Bytes())
if metadataLen > 0 {
bf.Write(metadata)
}
return found
case string:
return mime
}
panic("unreachable")
return bf.Bytes(), nil
}

func (p *implCompositeMetadata) Payload() []byte {
return p.data
// NewCompositeMetadataBuilder returns a CompositeMetadata builder.
func NewCompositeMetadataBuilder() *CompositeMetadataBuilder {
return &CompositeMetadataBuilder{}
}

func (p *implCompositeMetadata) encode() ([]byte, error) {
bs := make([]byte, 0)
switch v := p.mime.(type) {
case MIME:
bs = append(bs, 0x80|byte(v))
case string:
l := len(v)
if l > math.MaxInt8 {
return nil, fmt.Errorf("length of MIME type is over %d", math.MaxInt8)
}
bs = append(bs, byte(l))
bs = append(bs, []byte(v)...)
default:
panic("unreachable")
}
bs = append(bs, common.NewUint24(len(p.data)).Bytes()...)
bs = append(bs, p.data...)
return bs, nil
// NewCompositeMetadataBytes returns a CompositeMetadata.
func NewCompositeMetadataBytes(raw []byte) CompositeMetadata {
return raw
}
44 changes: 12 additions & 32 deletions extension/composite_metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,20 @@ import (
"fmt"
"testing"

"github.com/rsocket/rsocket-go/internal/common"
"github.com/stretchr/testify/assert"
)

func TestDecodeCompositeMetadata(t *testing.T) {
const mod = 3
bf := common.NewByteBuff()
for i := 0; i < 10; i++ {
var cm CompositeMetadata
if i%mod == 0 {
cm = NewCompositeMetadata("application/notWell", []byte(fmt.Sprintf("notWell_%d", i)))
} else {
cm = NewCompositeMetadata("text/plain", []byte(fmt.Sprintf("text_%d", i)))
}
bs, err := cm.encode()
if err != nil {
assert.Error(t, err, "encode composite metadata failed")
}
_, _ = bf.Write(bs)
func TestCompositeMetadataBuilder_Build(t *testing.T) {
cm, err := NewCompositeMetadataBuilder().
PushString("application/custom", fmt.Sprintf("not well")).
PushString("text/plain", "text").
PushWellKnownString(ApplicationJSON, `{"hello":"world"}`).
Build()
assert.NoError(t, err, "build composite metadata failed")
scanner := cm.Scanner()
for scanner.Scan() {
mimeType, metadata, err := scanner.Metadata()
assert.NoError(t, err, "scan metadata failed")
fmt.Println("mimeType:", mimeType, "metadata:", string(metadata))
}
bs := bf.Bytes()
cms, err := DecodeCompositeMetadata(bs)
if err != nil {
assert.Error(t, err, "decode composite metadata failed")
}

for k, v := range cms {
if k%mod == 0 {
assert.Equal(t, "application/notWell", v.MIME(), "bad MIME")
assert.Equal(t, fmt.Sprintf("notWell_%d", k), string(v.Payload()), "bad payload")
} else {
assert.Equal(t, "text/plain", v.MIME(), "bad MIME")
assert.Equal(t, fmt.Sprintf("text_%d", k), string(v.Payload()), "bad payload")
}
}

}

0 comments on commit de93b5c

Please sign in to comment.