304 lines
8.0 KiB
Go
304 lines
8.0 KiB
Go
// Copyright (C) MongoDB, Inc. 2017-present.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
package bson_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
|
|
"gitea.psichedelico.com/go/bson"
|
|
)
|
|
|
|
func ExampleRegistry_customEncoder() {
|
|
// Create a custom encoder for an integer type that multiplies the input
|
|
// value by -1 when encoding.
|
|
type negatedInt int
|
|
|
|
negatedIntType := reflect.TypeOf(negatedInt(0))
|
|
|
|
negatedIntEncoder := func(
|
|
_ bson.EncodeContext,
|
|
vw bson.ValueWriter,
|
|
val reflect.Value,
|
|
) error {
|
|
// All encoder implementations should check that val is valid and is of
|
|
// the correct type before proceeding.
|
|
if !val.IsValid() || val.Type() != negatedIntType {
|
|
return bson.ValueEncoderError{
|
|
Name: "negatedIntEncoder",
|
|
Types: []reflect.Type{negatedIntType},
|
|
Received: val,
|
|
}
|
|
}
|
|
|
|
// Negate val and encode as a BSON int32 if it can fit in 32 bits and a
|
|
// BSON int64 otherwise.
|
|
negatedVal := val.Int() * -1
|
|
if math.MinInt32 <= negatedVal && negatedVal <= math.MaxInt32 {
|
|
return vw.WriteInt32(int32(negatedVal))
|
|
}
|
|
return vw.WriteInt64(negatedVal)
|
|
}
|
|
|
|
reg := bson.NewRegistry()
|
|
reg.RegisterTypeEncoder(
|
|
negatedIntType,
|
|
bson.ValueEncoderFunc(negatedIntEncoder))
|
|
|
|
// Define a document that includes both int and negatedInt fields with the
|
|
// same value.
|
|
type myDocument struct {
|
|
Int int
|
|
NegatedInt negatedInt
|
|
}
|
|
doc := myDocument{
|
|
Int: 1,
|
|
NegatedInt: 1,
|
|
}
|
|
|
|
// Marshal the document as BSON. Expect that the int field is encoded to the
|
|
// same value and that the negatedInt field is encoded as the negated value.
|
|
buf := new(bytes.Buffer)
|
|
vw := bson.NewDocumentWriter(buf)
|
|
enc := bson.NewEncoder(vw)
|
|
enc.SetRegistry(reg)
|
|
err := enc.Encode(doc)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(bson.Raw(buf.Bytes()).String())
|
|
// Output: {"int": {"$numberInt":"1"},"negatedint": {"$numberInt":"-1"}}
|
|
}
|
|
|
|
func ExampleRegistry_customDecoder() {
|
|
// Create a custom decoder for a boolean type that can be stored in the
|
|
// database as a BSON boolean, int32, or int64. For our custom decoder, BSON
|
|
// int32 or int64 values are considered "true" if they are non-zero.
|
|
type lenientBool bool
|
|
|
|
lenientBoolType := reflect.TypeOf(lenientBool(true))
|
|
|
|
lenientBoolDecoder := func(
|
|
_ bson.DecodeContext,
|
|
vr bson.ValueReader,
|
|
val reflect.Value,
|
|
) error {
|
|
// All decoder implementations should check that val is valid, settable,
|
|
// and is of the correct kind before proceeding.
|
|
if !val.IsValid() || !val.CanSet() || val.Type() != lenientBoolType {
|
|
return bson.ValueDecoderError{
|
|
Name: "lenientBoolDecoder",
|
|
Types: []reflect.Type{lenientBoolType},
|
|
Received: val,
|
|
}
|
|
}
|
|
|
|
var result bool
|
|
switch vr.Type() {
|
|
case bson.TypeBoolean:
|
|
b, err := vr.ReadBoolean()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result = b
|
|
case bson.TypeInt32:
|
|
i32, err := vr.ReadInt32()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result = i32 != 0
|
|
case bson.TypeInt64:
|
|
i64, err := vr.ReadInt64()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result = i64 != 0
|
|
default:
|
|
return fmt.Errorf(
|
|
"received invalid BSON type to decode into lenientBool: %s",
|
|
vr.Type())
|
|
}
|
|
|
|
val.SetBool(result)
|
|
return nil
|
|
}
|
|
|
|
reg := bson.NewRegistry()
|
|
reg.RegisterTypeDecoder(
|
|
lenientBoolType,
|
|
bson.ValueDecoderFunc(lenientBoolDecoder))
|
|
|
|
// Marshal a BSON document with a single field "isOK" that is a non-zero
|
|
// integer value.
|
|
b, err := bson.Marshal(bson.M{"isOK": 1})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Now try to decode the BSON document to a struct with a field "IsOK" that
|
|
// is type lenientBool. Expect that the non-zero integer value is decoded
|
|
// as boolean true.
|
|
type MyDocument struct {
|
|
IsOK lenientBool `bson:"isOK"`
|
|
}
|
|
var doc MyDocument
|
|
dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(b)))
|
|
dec.SetRegistry(reg)
|
|
err = dec.Decode(&doc)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf("%+v\n", doc)
|
|
// Output: {IsOK:true}
|
|
}
|
|
|
|
func ExampleRegistry_RegisterKindEncoder() {
|
|
// Create a custom encoder that writes any Go type that has underlying type
|
|
// int32 as an a BSON int64. To do that, we register the encoder as a "kind"
|
|
// encoder for kind reflect.Int32. That way, even user-defined types with
|
|
// underlying type int32 will be encoded as a BSON int64.
|
|
int32To64Encoder := func(
|
|
_ bson.EncodeContext,
|
|
vw bson.ValueWriter,
|
|
val reflect.Value,
|
|
) error {
|
|
// All encoder implementations should check that val is valid and is of
|
|
// the correct type or kind before proceeding.
|
|
if !val.IsValid() || val.Kind() != reflect.Int32 {
|
|
return bson.ValueEncoderError{
|
|
Name: "int32To64Encoder",
|
|
Kinds: []reflect.Kind{reflect.Int32},
|
|
Received: val,
|
|
}
|
|
}
|
|
|
|
return vw.WriteInt64(val.Int())
|
|
}
|
|
|
|
// Create a default registry and register our int32-to-int64 encoder for
|
|
// kind reflect.Int32.
|
|
reg := bson.NewRegistry()
|
|
reg.RegisterKindEncoder(
|
|
reflect.Int32,
|
|
bson.ValueEncoderFunc(int32To64Encoder))
|
|
|
|
// Define a document that includes an int32, an int64, and a user-defined
|
|
// type "myInt" that has underlying type int32.
|
|
type myInt int32
|
|
type myDocument struct {
|
|
MyInt myInt
|
|
Int32 int32
|
|
Int64 int64
|
|
}
|
|
doc := myDocument{
|
|
Int32: 1,
|
|
Int64: 1,
|
|
MyInt: 1,
|
|
}
|
|
|
|
// Marshal the document as BSON. Expect that all fields are encoded as BSON
|
|
// int64 (represented as "$numberLong" when encoded as Extended JSON).
|
|
buf := new(bytes.Buffer)
|
|
vw := bson.NewDocumentWriter(buf)
|
|
enc := bson.NewEncoder(vw)
|
|
enc.SetRegistry(reg)
|
|
err := enc.Encode(doc)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(bson.Raw(buf.Bytes()).String())
|
|
// Output: {"myint": {"$numberLong":"1"},"int32": {"$numberLong":"1"},"int64": {"$numberLong":"1"}}
|
|
}
|
|
|
|
func ExampleRegistry_RegisterKindDecoder() {
|
|
// Create a custom decoder that can decode any integer value, including
|
|
// integer values encoded as floating point numbers, to any Go type
|
|
// with underlying type int64. To do that, we register the decoder as a
|
|
// "kind" decoder for kind reflect.Int64. That way, we can even decode to
|
|
// user-defined types with underlying type int64.
|
|
flexibleInt64KindDecoder := func(
|
|
_ bson.DecodeContext,
|
|
vr bson.ValueReader,
|
|
val reflect.Value,
|
|
) error {
|
|
// All decoder implementations should check that val is valid, settable,
|
|
// and is of the correct kind before proceeding.
|
|
if !val.IsValid() || !val.CanSet() || val.Kind() != reflect.Int64 {
|
|
return bson.ValueDecoderError{
|
|
Name: "flexibleInt64KindDecoder",
|
|
Kinds: []reflect.Kind{reflect.Int64},
|
|
Received: val,
|
|
}
|
|
}
|
|
|
|
var result int64
|
|
switch vr.Type() {
|
|
case bson.TypeInt64:
|
|
i64, err := vr.ReadInt64()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result = i64
|
|
case bson.TypeInt32:
|
|
i32, err := vr.ReadInt32()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result = int64(i32)
|
|
case bson.TypeDouble:
|
|
d, err := vr.ReadDouble()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
i64 := int64(d)
|
|
// Make sure the double field is an integer value.
|
|
if d != float64(i64) {
|
|
return fmt.Errorf("double %f is not an integer value", d)
|
|
}
|
|
result = i64
|
|
default:
|
|
return fmt.Errorf(
|
|
"received invalid BSON type to decode into int64: %s",
|
|
vr.Type())
|
|
}
|
|
|
|
val.SetInt(result)
|
|
return nil
|
|
}
|
|
|
|
reg := bson.NewRegistry()
|
|
reg.RegisterKindDecoder(
|
|
reflect.Int64,
|
|
bson.ValueDecoderFunc(flexibleInt64KindDecoder))
|
|
|
|
// Marshal a BSON document with fields that are mixed numeric types but all
|
|
// hold integer values (i.e. values with no fractional part).
|
|
b, err := bson.Marshal(bson.M{"myInt": float64(8), "int64": int32(9)})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Now try to decode the BSON document to a struct with fields
|
|
// that is type int32. Expect that the float value is successfully decoded.
|
|
type myInt int64
|
|
type myDocument struct {
|
|
MyInt myInt
|
|
Int64 int64
|
|
}
|
|
var doc myDocument
|
|
dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(b)))
|
|
dec.SetRegistry(reg)
|
|
err = dec.Decode(&doc)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf("%+v\n", doc)
|
|
// Output: {MyInt:8 Int64:9}
|
|
}
|