Skip to content

Commit 992017a

Browse files
author
Divjot Arora
authored
GODRIVER-904 Improve documentation for registering custom codecs (#276)
1 parent ed1cef6 commit 992017a

File tree

3 files changed

+159
-23
lines changed

3 files changed

+159
-23
lines changed

bson/bsoncodec/doc.go

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,51 @@
2727
//
2828
// Registry and RegistryBuilder
2929
//
30-
// A Registry is an immutable store for ValueEncoders, ValueDecoders, and a type map. For looking up
31-
// ValueEncoders and Decoders the Registry first attempts to find a ValueEncoder or ValueDecoder for
32-
// the type provided; if one cannot be found it then checks to see if a registered ValueEncoder or
33-
// ValueDecoder exists for an interface the type implements. Finally, the reflect.Kind of the type
34-
// is used to lookup a default ValueEncoder or ValueDecoder for that kind. If no ValueEncoder or
35-
// ValueDecoder can be found, an error is returned.
36-
//
37-
// The Registry also holds a type map. This allows users to retrieve the Go type that should be used
38-
// when decoding a BSON value into an empty interface. This is primarily only used for the empty
39-
// interface ValueDecoder.
40-
//
41-
// A RegistryBuilder is used to construct a Registry. The Register methods are used to associate
42-
// either a reflect.Type or a reflect.Kind with a ValueEncoder or ValueDecoder. A RegistryBuilder
43-
// returned from NewRegistryBuilder contains no registered ValueEncoders nor ValueDecoders and
44-
// contains an empty type map.
45-
//
46-
// The RegisterTypeMapEntry method handles associating a BSON type with a Go type. For example, if
47-
// you want to decode BSON int64 and int32 values into Go int instances, you would do the following:
48-
//
49-
// var regbuilder *RegistryBuilder = ... intType := reflect.TypeOf(int(0))
50-
// regbuilder.RegisterTypeMapEntry(bsontype.Int64, intType).RegisterTypeMapEntry(bsontype.Int32,
51-
// intType)
30+
// A Registry is an immutable store for ValueEncoders, ValueDecoders, and a type map. See the Registry type
31+
// documentation for examples of registering various custom encoders and decoders. A Registry can be constructed using a
32+
// RegistryBuilder, which handles three main types of codecs:
33+
//
34+
// 1. Type encoders/decoders - These can be registered using the RegisterTypeEncoder and RegisterTypeDecoder methods.
35+
// The registered codec will be invoked when encoding/decoding a value whose type matches the registered type exactly.
36+
// If the registered type is an interface, the codec will be invoked when encoding or decoding values whose type is the
37+
// interface, but not for values with concrete types that implement the interface.
38+
//
39+
// 2. Hook encoders/decoders - These can be registered using the RegisterHookEncoder and RegisterHookDecoder methods.
40+
// These methods only accept interface types and the registered codecs will be invoked when encoding or decoding values
41+
// whose types implement the interface. An example of a hook defined by the driver is bson.Marshaler. The driver will
42+
// call the MarshalBSON method for any value whose type implements bson.Marshaler, regardless of the value's concrete
43+
// type.
44+
//
45+
// 3. Type map entries - This can be used to associate a BSON type with a Go type. These type associations are used when
46+
// decoding into a bson.D/bson.M or a struct field of type interface{}. For example, by default, BSON int32 and int64
47+
// values decode as Go int32 and int64 instances, respectively, when decoding into a bson.D. The following code would
48+
// change the behavior so these values decode as Go int instances instead:
49+
//
50+
// intType := reflect.TypeOf(int(0))
51+
// registryBuilder.RegisterTypeMapEntry(bsontype.Int32, intType).RegisterTypeMapEntry(bsontype.Int64, intType)
52+
//
53+
// 4. Kind encoder/decoders - These can be registered using the RegisterDefaultEncoder and RegisterDefaultDecoder
54+
// methods. The registered codec will be invoked when encoding or decoding values whose reflect.Kind matches the
55+
// registered reflect.Kind as long as the value's type doesn't match a registered type or hook encoder/decoder first.
56+
// These methods should be used to change the behavior for all values for a specific kind.
57+
//
58+
// Registry Lookup Procedure
59+
//
60+
// When looking up an encoder in a Registry, the precedence rules are as follows:
61+
//
62+
// 1. A type encoder registered for the exact type of the value.
63+
//
64+
// 2. A hook encoder registered for an interface that is implemented by the value or by a pointer to the value. If the
65+
// value matches multiple hooks (e.g. the type implements bsoncodec.Marshaler and bsoncodec.ValueMarshaler), the first
66+
// one registered will be selected. Note that registries constructed using bson.NewRegistryBuilder have driver-defined
67+
// hooks registered for the bsoncodec.Marshaler, bsoncodec.ValueMarshaler, and bsoncodec.Proxy interfaces, so those
68+
// will take precedence over any new hooks.
69+
//
70+
// 3. A kind encoder registered for the value's kind.
71+
//
72+
// If all of these lookups fail to find an encoder, an error of type ErrNoEncoder is returned. The same precedence
73+
// rules apply for decoders, with the exception that an error of type ErrNoDecoder will be returned if no decoder is
74+
// found.
5275
//
5376
// DefaultValueEncoders and DefaultValueDecoders
5477
//
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (C) MongoDB, Inc. 2017-present.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
// not use this file except in compliance with the License. You may obtain
5+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
7+
package bsoncodec_test
8+
9+
import (
10+
"fmt"
11+
"math"
12+
"reflect"
13+
14+
"go.mongodb.org/mongo-driver/bson/bsoncodec"
15+
"go.mongodb.org/mongo-driver/bson/bsonrw"
16+
"go.mongodb.org/mongo-driver/bson/bsontype"
17+
)
18+
19+
func ExampleRegistry_customEncoder() {
20+
// Write a custom encoder for an integer type that is multiplied by -1 when encoding.
21+
22+
// To register the default encoders and decoders in addition to this custom one, use bson.NewRegistryBuilder
23+
// instead.
24+
rb := bsoncodec.NewRegistryBuilder()
25+
type negatedInt int
26+
27+
niType := reflect.TypeOf(negatedInt(0))
28+
encoder := func(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
29+
// All encoder implementations should check that val is valid and is of the correct type before proceeding.
30+
if !val.IsValid() || val.Type() != niType {
31+
return bsoncodec.ValueEncoderError{
32+
Name: "negatedIntEncodeValue",
33+
Types: []reflect.Type{niType},
34+
Received: val,
35+
}
36+
}
37+
38+
// Negate val and encode as a BSON int32 if it can fit in 32 bits and a BSON int64 otherwise.
39+
negatedVal := val.Int() * -1
40+
if math.MinInt32 <= negatedVal && negatedVal <= math.MaxInt32 {
41+
return vw.WriteInt32(int32(negatedVal))
42+
}
43+
return vw.WriteInt64(negatedVal)
44+
}
45+
46+
rb.RegisterTypeEncoder(niType, bsoncodec.ValueEncoderFunc(encoder))
47+
}
48+
49+
func ExampleRegistry_customDecoder() {
50+
// Write a custom decoder for a boolean type that can be stored in the database as a BSON boolean, int32, int64,
51+
// double, or null. BSON int32, int64, and double values are considered "true" in this decoder if they are
52+
// non-zero. BSON null values are always considered false.
53+
54+
// To register the default encoders and decoders in addition to this custom one, use bson.NewRegistryBuilder
55+
// instead.
56+
rb := bsoncodec.NewRegistryBuilder()
57+
type lenientBool bool
58+
59+
decoder := func(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
60+
// All decoder implementations should check that val is valid and settable and is of the correct kind
61+
// before proceeding.
62+
if !val.IsValid() || !val.CanSet() || val.Kind() != reflect.Bool {
63+
return bsoncodec.ValueDecoderError{
64+
Name: "lenientBoolDecodeValue",
65+
Kinds: []reflect.Kind{reflect.Bool},
66+
Received: val,
67+
}
68+
}
69+
70+
var result bool
71+
switch vr.Type() {
72+
case bsontype.Boolean:
73+
b, err := vr.ReadBoolean()
74+
if err != nil {
75+
return err
76+
}
77+
result = b
78+
case bsontype.Int32:
79+
i32, err := vr.ReadInt32()
80+
if err != nil {
81+
return err
82+
}
83+
result = i32 != 0
84+
case bsontype.Int64:
85+
i64, err := vr.ReadInt64()
86+
if err != nil {
87+
return err
88+
}
89+
result = i64 != 0
90+
case bsontype.Double:
91+
f64, err := vr.ReadDouble()
92+
if err != nil {
93+
return err
94+
}
95+
result = f64 != 0
96+
case bsontype.Null:
97+
if err := vr.ReadNull(); err != nil {
98+
return err
99+
}
100+
result = false
101+
default:
102+
return fmt.Errorf("received invalid BSON type to decode into lenientBool: %s", vr.Type())
103+
}
104+
105+
val.SetBool(result)
106+
return nil
107+
}
108+
109+
lbType := reflect.TypeOf(lenientBool(true))
110+
rb.RegisterTypeDecoder(lbType, bsoncodec.ValueDecoderFunc(decoder))
111+
}

bson/doc.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
// Package bson is a library for reading, writing, and manipulating BSON. BSON is a binary serialization format used to
88
// store documents and make remote procedure calls in MongoDB. The BSON specification is located at https://bsonspec.org.
9+
// The BSON library handles marshalling and unmarshalling of values through a configurable codec system. For a description
10+
// of the codec system and examples of registering custom codecs, see the bsoncodec package.
911
//
1012
// Raw BSON
1113
//
@@ -90,7 +92,7 @@
9092
// 1. omitempty: If the omitempty struct tag is specified on a field, the field will not be marshalled if it is set to
9193
// the zero value. By default, a struct field is only considered empty if the field's type implements the Zeroer
9294
// interface and the IsZero method returns true. Struct fields of types that do not implement Zeroer are always
93-
// marshalled as embedded documents.
95+
// marshalled as embedded documents. This tag should be used for all slice and map values.
9496
//
9597
// 2. minsize: If the minsize struct tag is specified on a field of type int64, uint, uint32, or uint64 and the value of
9698
// the field can fit in a signed int32, the field will be serialized as a BSON int32 rather than a BSON int64. For other

0 commit comments

Comments
 (0)