Registering custom encoder seems to NewRegistry doesn't contain default encoders/decoders

I’m trying to add a custom encoder and decoder for a Golang struct type, which must be stored in Mongo as a string. Previously, our mongo driver used no custom encoders (only the BSONOptions configuration) and was configured like so:

	bsonOptions := &options.BSONOptions{
		NilMapAsEmpty:   true,
		NilSliceAsEmpty: true,
	}

	client, err := gomongo.Connect(context.Background(),
		options.Client().
			ApplyURI(m.config.MongoURL).
			SetWriteConcern(writeconcern.Majority()).
			SetReadConcern(readconcern.Majority()).
			SetBSONOptions(bsonOptions))

I have now changed this to:

	bsonOptions := &options.BSONOptions{
		NilMapAsEmpty:   true,
		NilSliceAsEmpty: true,
	}

	client, err := gomongo.Connect(context.Background(),
		options.Client().
			ApplyURI(m.config.MongoURL).
			SetWriteConcern(writeconcern.Majority()).
			SetReadConcern(readconcern.Majority()).
			SetBSONOptions(bsonOptions).
			SetRegistry(buildRegistry()))

where the buildRegistry funciton is defined as:

func buildRegistry() *bsoncodec.Registry {
	registry := bsoncodec.NewRegistry()
	registry.RegisterTypeEncoder(reflect.TypeOf(arns.ARN{}),
		bsoncodec.ValueEncoderFunc(arnsEncodeValue))
	registry.RegisterTypeDecoder(reflect.TypeOf(arns.ARN{}),
		bsoncodec.ValueDecoderFunc(arnsDecodeValue))
	return registry
}

(I’m leaving out the definitions of arnsDecodeValue and arnsEncodeValue for brevity, but can include them if they’re relevant.)

arns.ARN is a struct type. If I comment out the four lines that register the encoder and decoder, so that I’m just using a default registry, everything works as it always has. As soon as I register either an encoder or a decoder, however, every single attempt to query data from the backend results in an identical error message:

cannot marshal type primitive.M to a BSON Document: no encoder found for primitive.M

It seems as though the addition of a single type-encoder (or decoder) overrides the Registry’s ability to find and use the default decoder for the basic primitive types. It’s not clear to me if this is a bug or if something is misconfigured, and any help or advice would be greatly appreciated.

The issue seems to be the use of bsoncodec.NewRegistry instead of bson.NewRegistry! The former is lower-level and does not include default encoders/decoders at all. I let my IDE’s autocomplete get the better of me, and then when looking at the stated behavior in the docs, didn’t realize that I was looking at the stated behavior of a different function with the same name.

Hey @Xander_Flood , note that even when you register a type encoder with bson.NewRegistry, you still have to provide the encoding logic for any types you plan on using. The default logic is not inherited in this case. Here is an example:

package main

import (
	"context"
	"reflect"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/bsoncodec"
	"go.mongodb.org/mongo-driver/bson/bsonrw"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type ARN struct {
	X int `bson:"x"`
}

func arnsEncodeValue(bsoncodec.EncodeContext, bsonrw.ValueWriter, reflect.Value) error {
	return nil
}

func main() {
	// Create a registry that decodes nothing.
	registry := bson.NewRegistry()

	registry.RegisterTypeEncoder(reflect.TypeOf(ARN{}),
		bsoncodec.ValueEncoderFunc(arnsEncodeValue))

	client, err := mongo.Connect(context.Background(),
		options.Client().
			ApplyURI("mongodb://localhost:27017").
			SetRegistry(registry))

	if err != nil {
		panic(err)
	}

	coll := client.Database("test").Collection("coll")
	_, err = coll.InsertOne(context.Background(), primitive.M{"x": 1})
	if err != nil {
		panic(err) // panic: cannot marshal type primitive.M to a BSON Document: no encoder found for primitive.M
	}
}

To resolve this, you could use the following value encoder function:

func arnsEncodeValue(_ bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
	cdoc := val.Interface().(bsoncore.Document)

	return bsonrw.Copier{}.CopyDocumentFromBytes(vw, cdoc)
}