File: registry_examples_test.go

package info (click to toggle)
golang-mongodb-mongo-driver 1.17.1%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental, sid, trixie
  • size: 25,988 kB
  • sloc: perl: 533; ansic: 491; python: 432; sh: 327; makefile: 174
file content (293 lines) | stat: -rw-r--r-- 8,096 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// 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 bsoncodec_test

import (
	"fmt"
	"math"
	"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/bsontype"
)

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(
		_ bsoncodec.EncodeContext,
		vw bsonrw.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 bsoncodec.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,
		bsoncodec.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.
	b, err := bson.MarshalWithRegistry(reg, doc)
	if err != nil {
		panic(err)
	}
	fmt.Println(bson.Raw(b).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(
		_ bsoncodec.DecodeContext,
		vr bsonrw.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 bsoncodec.ValueDecoderError{
				Name:     "lenientBoolDecoder",
				Types:    []reflect.Type{lenientBoolType},
				Received: val,
			}
		}

		var result bool
		switch vr.Type() {
		case bsontype.Boolean:
			b, err := vr.ReadBoolean()
			if err != nil {
				return err
			}
			result = b
		case bsontype.Int32:
			i32, err := vr.ReadInt32()
			if err != nil {
				return err
			}
			result = i32 != 0
		case bsontype.Int64:
			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,
		bsoncodec.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
	err = bson.UnmarshalWithRegistry(reg, b, &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(
		_ bsoncodec.EncodeContext,
		vw bsonrw.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 bsoncodec.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,
		bsoncodec.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).
	b, err := bson.MarshalWithRegistry(reg, doc)
	if err != nil {
		panic(err)
	}
	fmt.Println(bson.Raw(b).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(
		_ bsoncodec.DecodeContext,
		vr bsonrw.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 bsoncodec.ValueDecoderError{
				Name:     "flexibleInt64KindDecoder",
				Kinds:    []reflect.Kind{reflect.Int64},
				Received: val,
			}
		}

		var result int64
		switch vr.Type() {
		case bsontype.Int64:
			i64, err := vr.ReadInt64()
			if err != nil {
				return err
			}
			result = i64
		case bsontype.Int32:
			i32, err := vr.ReadInt32()
			if err != nil {
				return err
			}
			result = int64(i32)
		case bsontype.Double:
			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,
		bsoncodec.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
	err = bson.UnmarshalWithRegistry(reg, b, &doc)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", doc)
	// Output: {MyInt:8 Int64:9}
}