Skip to content

Commit

Permalink
Add support for all scalar types to bsonproto (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlekSi authored Dec 27, 2023
1 parent 77005c8 commit a7769a9
Show file tree
Hide file tree
Showing 14 changed files with 511 additions and 71 deletions.
85 changes: 84 additions & 1 deletion bsonproto/binary.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,86 @@
package bsonproto

type Binary struct{}
import (
"encoding/binary"
"fmt"
)

//go:generate go run golang.org/x/tools/cmd/stringer@latest -linecomment -type BinarySubtype

// BinarySubtype represents BSON Binary's subtype.
type BinarySubtype byte

const (
// BinaryGeneric represents a BSON Binary generic subtype.
BinaryGeneric = BinarySubtype(0x00) // generic

// BinaryFunction represents a BSON Binary function subtype
BinaryFunction = BinarySubtype(0x01) // function

// BinaryGenericOld represents a BSON Binary generic-old subtype.
BinaryGenericOld = BinarySubtype(0x02) // generic-old

// BinaryUUIDOld represents a BSON Binary UUID old subtype.
BinaryUUIDOld = BinarySubtype(0x03) // uuid-old

// BinaryUUID represents a BSON Binary UUID subtype.
BinaryUUID = BinarySubtype(0x04) // uuid

// BinaryMD5 represents a BSON Binary MD5 subtype.
BinaryMD5 = BinarySubtype(0x05) // md5

// BinaryEncrypted represents a BSON Binary encrypted subtype.
BinaryEncrypted = BinarySubtype(0x06) // encrypted

// BinaryUser represents a BSON Binary user-defined subtype.
BinaryUser = BinarySubtype(0x80) // user
)

// Binary represents BSON scalar type binary.
type Binary struct {
B []byte
Subtype BinarySubtype
}

// SizeBinary returns a size of the encoding of v [Binary] in bytes.
func SizeBinary(v Binary) int {
return len(v.B) + 5
}

// EncodeBinary encodes [Binary] value v into b.
//
// b must be at least len(v.B)+5 ([SizeBinary]) bytes long; otherwise, EncodeBinary will panic.
// Only b[0:len(v.B)+5] bytes are modified.
func EncodeBinary(b []byte, v Binary) {
i := len(v.B)

binary.LittleEndian.PutUint32(b, uint32(i))
b[4] = byte(v.Subtype)
copy(b[5:5+i], v.B)
}

// DecodeBinary decodes [Binary] value from b.
//
// If there is not enough bytes, DecodeBinary will return a wrapped [ErrDecodeShortInput].
// If the input is otherwise invalid, a wrapped [ErrDecodeInvalidInput] is returned.
func DecodeBinary(b []byte) (Binary, error) {
var res Binary

if len(b) < 5 {
return res, fmt.Errorf("DecodeBinary: expected at least 5 bytes, got %d: %w", len(b), ErrDecodeShortInput)
}

i := int(binary.LittleEndian.Uint32(b))
if e := 5 + i; len(b) < e {
return res, fmt.Errorf("DecodeBinary: expected at least %d bytes, got %d: %w", e, len(b), ErrDecodeShortInput)
}

res.Subtype = BinarySubtype(b[4])

if i > 0 {
res.B = make([]byte, i)
copy(res.B, b[5:5+i])
}

return res, nil
}
39 changes: 39 additions & 0 deletions bsonproto/binarysubtype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions bsonproto/bool.go
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
package bsonproto

import "fmt"

// SizeBool is a size of the encoding of bool in bytes.
const SizeBool = 1

// EncodeBool encodes bool value v into b.
//
// b must be at least 1 ([SizeBool]) byte long; otherwise, EncodeBool will panic.
// Only b[0] is modified.
func EncodeBool(b []byte, v bool) {
if v {
b[0] = 0x01
} else {
b[0] = 0x00
}
}

// DecodeBool decodes bool value from b.
//
// If there is not enough bytes, DecodeBool will return a wrapped [ErrDecodeShortInput].
func DecodeBool(b []byte) (bool, error) {
if len(b) == 0 {
return false, fmt.Errorf("DecodeBool: expected at least 1 byte, got 0: %w", ErrDecodeShortInput)
}

switch b[0] {
case 0x00:
return false, nil
case 0x01:
return true, nil
default:
return false, fmt.Errorf("DecodeBool: expected 0x00 or 0x01, got 0x%02x: %w", b[0], ErrDecodeInvalidInput)
}
}
64 changes: 52 additions & 12 deletions bsonproto/bsonproto.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,31 @@ func Size[T ScalarType](v T) int {

// SizeAny returns a size of the encoding of value v in bytes.
//
// It panics if v is not a ScalarType.
// It panics if v is not a [ScalarType].
func SizeAny(v any) int {
switch v := v.(type) {
case float64:
return SizeFloat64(v)
return SizeFloat64
case string:
return SizeString(v)
case Binary:
return SizeBinary(v)
case ObjectID:
return SizeObjectID
case bool:
return SizeBool
case time.Time:
return SizeTime
case NullType:
return 0
case Regex:
return SizeRegex(v)
case int32:
return SizeInt32(v)
return SizeInt32
case Timestamp:
return SizeTimestamp
case int64:
return SizeInt64(v)
return SizeInt64
default:
panic(fmt.Sprintf("unsupported type %T", v))
}
Expand All @@ -48,15 +62,29 @@ func Encode[T ScalarType](b []byte, v T) {
// b must be at least Size(v) bytes long; otherwise, EncodeAny will panic.
// Only b[0:Size(v)] bytes are modified.
//
// It panics if v is not a ScalarType.
// It panics if v is not a [ScalarType].
func EncodeAny(b []byte, v any) {
switch v := v.(type) {
case float64:
EncodeFloat64(b, v)
case string:
EncodeString(b, v)
case Binary:
EncodeBinary(b, v)
case ObjectID:
EncodeObjectID(b, v)
case bool:
EncodeBool(b, v)
case time.Time:
EncodeTime(b, v)
case NullType:
// nothing
case Regex:
EncodeRegex(b, v)
case int32:
EncodeInt32(b, v)
case Timestamp:
EncodeTimestamp(b, v)
case int64:
EncodeInt64(b, v)
default:
Expand All @@ -66,29 +94,41 @@ func EncodeAny(b []byte, v any) {

// Decode decodes value from b into v.
//
// If there is not enough bytes, Decode will return a wrapped ErrDecodeShortInput.
// If the input is otherwise invalid, a wrapped ErrDecodeInvalidInput is returned.
//
// If the value can't be decoded, a wrapped ErrDecodeInvalidInput is returned.
// If there is not enough bytes, Decode will return a wrapped [ErrDecodeShortInput].
// If the input is otherwise invalid, a wrapped [ErrDecodeInvalidInput] is returned.
func Decode[T ScalarType](b []byte, v *T) error {
return DecodeAny(b, v)
}

// DecodeAny decodes value from b into v.
//
// If there is not enough bytes, DecodeAny will return a wrapped ErrDecodeShortInput.
// If the input is otherwise invalid, a wrapped ErrDecodeInvalidInput is returned.
// If there is not enough bytes, DecodeAny will return a wrapped [ErrDecodeShortInput].
// If the input is otherwise invalid, a wrapped [ErrDecodeInvalidInput] is returned.
//
// It panics if v is not a pointer to ScalarType.
// It panics if v is not a pointer to [ScalarType].
func DecodeAny(b []byte, v any) error {
var err error
switch v := v.(type) {
case *float64:
*v, err = DecodeFloat64(b)
case *string:
*v, err = DecodeString(b)
case *Binary:
*v, err = DecodeBinary(b)
case *ObjectID:
*v, err = DecodeObjectID(b)
case *bool:
*v, err = DecodeBool(b)
case *time.Time:
*v, err = DecodeTime(b)
case *NullType:
// nothing
case *Regex:
*v, err = DecodeRegex(b)
case *int32:
*v, err = DecodeInt32(b)
case *Timestamp:
*v, err = DecodeTimestamp(b)
case *int64:
*v, err = DecodeInt64(b)
default:
Expand Down
Loading

0 comments on commit a7769a9

Please sign in to comment.