174 lines
3.4 KiB
Go
174 lines
3.4 KiB
Go
package multiprotocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/multiformats/go-varint"
|
|
)
|
|
|
|
type Component struct {
|
|
bytes []byte
|
|
protocol Protocol
|
|
offset int
|
|
}
|
|
|
|
func (c *Component) Bytes() []byte {
|
|
return c.bytes
|
|
}
|
|
|
|
func (c *Component) MarshalBinary() ([]byte, error) {
|
|
return c.Bytes(), nil
|
|
}
|
|
|
|
func (c *Component) UnmarshalBinary(data []byte) error {
|
|
_, comp, err := readComponent(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*c = comp
|
|
return nil
|
|
}
|
|
|
|
func (c *Component) MarshalText() ([]byte, error) {
|
|
return []byte(c.String()), nil
|
|
}
|
|
|
|
func (c *Component) UnmarshalText(data []byte) error {
|
|
bytes, err := stringToBytes(string(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, comp, err := readComponent(bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*c = comp
|
|
return nil
|
|
}
|
|
|
|
func (c *Component) MarshalJSON() ([]byte, error) {
|
|
txt, err := c.MarshalText()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return json.Marshal(string(txt))
|
|
}
|
|
|
|
func (c *Component) UnmarshalJSON(data []byte) error {
|
|
var v string
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.UnmarshalText([]byte(v))
|
|
}
|
|
|
|
func (c *Component) Equal(o Multiprotocol) bool {
|
|
return bytes.Equal(c.bytes, o.Bytes())
|
|
}
|
|
|
|
func (c *Component) Protocols() []Protocol {
|
|
return []Protocol{c.protocol}
|
|
}
|
|
|
|
func (c *Component) ValueForProtocol(code int) (string, error) {
|
|
if c.protocol.Code != code {
|
|
return "", ErrProtocolNotFound
|
|
}
|
|
return c.Value(), nil
|
|
}
|
|
|
|
func (c *Component) Protocol() Protocol {
|
|
return c.protocol
|
|
}
|
|
|
|
func (c *Component) RawValue() []byte {
|
|
return c.bytes[c.offset:]
|
|
}
|
|
|
|
func (c *Component) Value() string {
|
|
if c.protocol.Transcoder == nil {
|
|
return ""
|
|
}
|
|
value, err := c.protocol.Transcoder.BytesToString(c.bytes[c.offset:])
|
|
if err != nil {
|
|
// This Component must have been checked.
|
|
panic(err)
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (c *Component) String() string {
|
|
var b strings.Builder
|
|
c.writeTo(&b)
|
|
return b.String()
|
|
}
|
|
|
|
// writeTo is an efficient, private function for string-formatting a multiprotocol.
|
|
// Trust me, we tend to allocate a lot when doing this.
|
|
func (c *Component) writeTo(b *strings.Builder) {
|
|
b.WriteByte('/')
|
|
b.WriteString(c.protocol.Name)
|
|
value := c.Value()
|
|
if len(value) == 0 {
|
|
return
|
|
}
|
|
|
|
if value[0] != '/' {
|
|
b.WriteByte('/')
|
|
}
|
|
|
|
b.WriteString(value)
|
|
}
|
|
|
|
// NewComponent constructs a new multiprotocol component
|
|
func NewComponent(protocol, value string) (*Component, error) {
|
|
p := ProtocolWithName(protocol)
|
|
if p.Code == 0 {
|
|
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
|
|
}
|
|
if p.Transcoder != nil {
|
|
bts, err := p.Transcoder.StringToBytes(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newComponent(p, bts), nil
|
|
} else if value != "" {
|
|
return nil, fmt.Errorf("protocol %s doesn't take a value", p.Name)
|
|
}
|
|
return newComponent(p, nil), nil
|
|
// TODO: handle path /?
|
|
}
|
|
|
|
func newComponent(protocol Protocol, bvalue []byte) *Component {
|
|
size := len(bvalue)
|
|
size += len(protocol.VCode)
|
|
if protocol.Size < 0 {
|
|
size += varint.UvarintSize(uint64(len(bvalue)))
|
|
}
|
|
maddr := make([]byte, size)
|
|
var offset int
|
|
offset += copy(maddr[offset:], protocol.VCode)
|
|
if protocol.Size < 0 {
|
|
offset += binary.PutUvarint(maddr[offset:], uint64(len(bvalue)))
|
|
}
|
|
copy(maddr[offset:], bvalue)
|
|
|
|
// For debugging
|
|
if len(maddr) != offset+len(bvalue) {
|
|
panic("incorrect length")
|
|
}
|
|
|
|
return &Component{
|
|
bytes: maddr,
|
|
protocol: protocol,
|
|
offset: offset,
|
|
}
|
|
}
|
|
|