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 bson
8 9 import (
10 "errors"
11 "reflect"
12 "sync"
13 14 "go.mongodb.org/mongo-driver/bson/bsoncodec"
15 "go.mongodb.org/mongo-driver/bson/bsonrw"
16 )
17 18 // This pool is used to keep the allocations of Encoders down. This is only used for the Marshal*
19 // methods and is not consumable from outside of this package. The Encoders retrieved from this pool
20 // must have both Reset and SetRegistry called on them.
21 var encPool = sync.Pool{
22 New: func() interface{} {
23 return new(Encoder)
24 },
25 }
26 27 // An Encoder writes a serialization format to an output stream. It writes to a bsonrw.ValueWriter
28 // as the destination of BSON data.
29 type Encoder struct {
30 ec bsoncodec.EncodeContext
31 vw bsonrw.ValueWriter
32 33 errorOnInlineDuplicates bool
34 intMinSize bool
35 stringifyMapKeysWithFmt bool
36 nilMapAsEmpty bool
37 nilSliceAsEmpty bool
38 nilByteSliceAsEmpty bool
39 omitZeroStruct bool
40 useJSONStructTags bool
41 }
42 43 // NewEncoder returns a new encoder that uses the DefaultRegistry to write to vw.
44 func NewEncoder(vw bsonrw.ValueWriter) (*Encoder, error) {
45 // TODO:(GODRIVER-2719): Remove error return value.
46 if vw == nil {
47 return nil, errors.New("cannot create a new Encoder with a nil ValueWriter")
48 }
49 50 return &Encoder{
51 ec: bsoncodec.EncodeContext{Registry: DefaultRegistry},
52 vw: vw,
53 }, nil
54 }
55 56 // NewEncoderWithContext returns a new encoder that uses EncodeContext ec to write to vw.
57 //
58 // Deprecated: Use [NewEncoder] and use the Encoder configuration methods to set the desired marshal
59 // behavior instead.
60 func NewEncoderWithContext(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter) (*Encoder, error) {
61 if ec.Registry == nil {
62 ec = bsoncodec.EncodeContext{Registry: DefaultRegistry}
63 }
64 if vw == nil {
65 return nil, errors.New("cannot create a new Encoder with a nil ValueWriter")
66 }
67 68 return &Encoder{
69 ec: ec,
70 vw: vw,
71 }, nil
72 }
73 74 // Encode writes the BSON encoding of val to the stream.
75 //
76 // See [Marshal] for details about BSON marshaling behavior.
77 func (e *Encoder) Encode(val interface{}) error {
78 if marshaler, ok := val.(Marshaler); ok {
79 // TODO(skriptble): Should we have a MarshalAppender interface so that we can have []byte reuse?
80 buf, err := marshaler.MarshalBSON()
81 if err != nil {
82 return err
83 }
84 return bsonrw.Copier{}.CopyDocumentFromBytes(e.vw, buf)
85 }
86 87 encoder, err := e.ec.LookupEncoder(reflect.TypeOf(val))
88 if err != nil {
89 return err
90 }
91 92 // Copy the configurations applied to the Encoder over to the EncodeContext, which actually
93 // communicates those configurations to the default ValueEncoders.
94 if e.errorOnInlineDuplicates {
95 e.ec.ErrorOnInlineDuplicates()
96 }
97 if e.intMinSize {
98 e.ec.MinSize = true
99 }
100 if e.stringifyMapKeysWithFmt {
101 e.ec.StringifyMapKeysWithFmt()
102 }
103 if e.nilMapAsEmpty {
104 e.ec.NilMapAsEmpty()
105 }
106 if e.nilSliceAsEmpty {
107 e.ec.NilSliceAsEmpty()
108 }
109 if e.nilByteSliceAsEmpty {
110 e.ec.NilByteSliceAsEmpty()
111 }
112 if e.omitZeroStruct {
113 e.ec.OmitZeroStruct()
114 }
115 if e.useJSONStructTags {
116 e.ec.UseJSONStructTags()
117 }
118 119 return encoder.EncodeValue(e.ec, e.vw, reflect.ValueOf(val))
120 }
121 122 // Reset will reset the state of the Encoder, using the same *EncodeContext used in
123 // the original construction but using vw.
124 func (e *Encoder) Reset(vw bsonrw.ValueWriter) error {
125 // TODO:(GODRIVER-2719): Remove error return value.
126 e.vw = vw
127 return nil
128 }
129 130 // SetRegistry replaces the current registry of the Encoder with r.
131 func (e *Encoder) SetRegistry(r *bsoncodec.Registry) error {
132 // TODO:(GODRIVER-2719): Remove error return value.
133 e.ec.Registry = r
134 return nil
135 }
136 137 // SetContext replaces the current EncodeContext of the encoder with ec.
138 //
139 // Deprecated: Use the Encoder configuration methods set the desired marshal behavior instead.
140 func (e *Encoder) SetContext(ec bsoncodec.EncodeContext) error {
141 // TODO:(GODRIVER-2719): Remove error return value.
142 e.ec = ec
143 return nil
144 }
145 146 // ErrorOnInlineDuplicates causes the Encoder to return an error if there is a duplicate field in
147 // the marshaled BSON when the "inline" struct tag option is set.
148 func (e *Encoder) ErrorOnInlineDuplicates() {
149 e.errorOnInlineDuplicates = true
150 }
151 152 // IntMinSize causes the Encoder to marshal Go integer values (int, int8, int16, int32, int64, uint,
153 // uint8, uint16, uint32, or uint64) as the minimum BSON int size (either 32 or 64 bits) that can
154 // represent the integer value.
155 func (e *Encoder) IntMinSize() {
156 e.intMinSize = true
157 }
158 159 // StringifyMapKeysWithFmt causes the Encoder to convert Go map keys to BSON document field name
160 // strings using fmt.Sprint instead of the default string conversion logic.
161 func (e *Encoder) StringifyMapKeysWithFmt() {
162 e.stringifyMapKeysWithFmt = true
163 }
164 165 // NilMapAsEmpty causes the Encoder to marshal nil Go maps as empty BSON documents instead of BSON
166 // null.
167 func (e *Encoder) NilMapAsEmpty() {
168 e.nilMapAsEmpty = true
169 }
170 171 // NilSliceAsEmpty causes the Encoder to marshal nil Go slices as empty BSON arrays instead of BSON
172 // null.
173 func (e *Encoder) NilSliceAsEmpty() {
174 e.nilSliceAsEmpty = true
175 }
176 177 // NilByteSliceAsEmpty causes the Encoder to marshal nil Go byte slices as empty BSON binary values
178 // instead of BSON null.
179 func (e *Encoder) NilByteSliceAsEmpty() {
180 e.nilByteSliceAsEmpty = true
181 }
182 183 // TODO(GODRIVER-2820): Update the description to remove the note about only examining exported
184 // TODO struct fields once the logic is updated to also inspect private struct fields.
185 186 // OmitZeroStruct causes the Encoder to consider the zero value for a struct (e.g. MyStruct{})
187 // as empty and omit it from the marshaled BSON when the "omitempty" struct tag option is set.
188 //
189 // Note that the Encoder only examines exported struct fields when determining if a struct is the
190 // zero value. It considers pointers to a zero struct value (e.g. &MyStruct{}) not empty.
191 func (e *Encoder) OmitZeroStruct() {
192 e.omitZeroStruct = true
193 }
194 195 // UseJSONStructTags causes the Encoder to fall back to using the "json" struct tag if a "bson"
196 // struct tag is not specified.
197 func (e *Encoder) UseJSONStructTags() {
198 e.useJSONStructTags = true
199 }
200