I am using Go official MongoDB driver. Is there any way to validate struct fields using BSON tags, the way how GORM pkg does?
2 Answers
You mean as struct tags? No, not directly. And there is a good reason for that: software libraries more and more adopt the "do one thing and do it good" approach. In other words: use the right tool for the job.
But validating your structs is easy enough. You can utilize the vastly powerful github.com/asaskevich/govalidator, for example:
package main
import (
"testing"
"github.com/asaskevich/govalidator"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Author struct {
FirstName string `valid:"stringlength(3|20)" bson:"given_name" json:"given_name"`
LastName string `valid:"stringlength(2|20)" bson:"surname" json:"surname"`
}
type Book struct {
// Here, if ID is set, it needs to be a valid ObjectID
ID primitive.ObjectID `valid:"oid,optional" bson:"_id,omitempty" json:"_id,omitempty"`
ISBN string `valid:"isbn13,optional" bson:"isbn,omitempty" json:"isbn,omitempty"`
Title string `valid:"stringlength(5|20)" bson:"title" json:"title"`
// This instructs govalidator to validate the referenced struct, even when
// it is a pointer
Author *Author `valid:"" bson:"author" json:"author"`
}
// ObjectIDValidator validates whether the given type is a primitive.ObjectId
// and whether its value is valid.
// govalidator only validates a string as ObjectId, so we implement a little wrapper function...
func ObjectIDValidator(inner, outer interface{}) bool {
oid := inner.(primitive.ObjectID)
str := oid.Hex()
return govalidator.IsMongoID(str)
}
// ...and add it to the validators you can use as a struct tag
func init() {
govalidator.CustomTypeTagMap.Set("oid", govalidator.CustomTypeValidator(ObjectIDValidator))
}
func TestValidity(t *testing.T) {
testCases := []struct {
desc string
book Book
expectedFail bool
}{
{
desc: "A book with an invalid ISBN",
book: Book{
ID: primitive.NewObjectID(),
Title: "foobar",
ISBN: "abc",
Author: &Author{FirstName: "Foo", LastName: "Bar"},
},
expectedFail: true,
},
{
desc: "A perfectly valid (and good!) book",
book: Book{
ID: primitive.NewObjectID(),
Title: "Neuromancer",
ISBN: "978-0441569595",
Author: &Author{FirstName: "William", LastName: "Gibson"},
},
expectedFail: false,
},
{
desc: "Still a good book, but with the title cut short",
book: Book{
ID: primitive.NewObjectID(),
Title: "Neur",
ISBN: "978-0441569595",
Author: &Author{FirstName: "William", LastName: "Gibson"},
},
expectedFail: true,
},
{
desc: "Still a good book, only the author's name was cut short",
book: Book{
ID: primitive.NewObjectID(),
Title: "Neuromancer",
ISBN: "978-0441569595",
Author: &Author{FirstName: "W", LastName: "Gibson"},
},
expectedFail: true,
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
ok, err := govalidator.ValidateStruct(tC.book)
switch {
case !ok && !tC.expectedFail:
t.Errorf("%#v unexpectedly did not validate as a Book: %s", tC.book, err)
return
case ok && tC.expectedFail:
t.Errorf("%#v unexpectedly validated as a Book!", tC.book)
return
case (!ok && tC.expectedFail) || (ok && !tC.expectedFail):
t.Logf("Just as planned")
}
})
}
}

- 19,711
- 6
- 65
- 89
Current version of mongo-go does not support validation.
The valid / processed tags are listed at bson@Structs
:
The following struct tags can be used to configure behavior:
omitempty
: If the omitempty struct tag is specified on a field, the field will not be marshalled if it is set to the zero value. By default, a struct field is only considered empty if the field's type implements the Zeroer interface and the IsZero method returns true. Struct fields of types that do not implement Zeroer are always marshalled as embedded documents. This tag should be used for all slice and map values.
minsize
: If the minsize struct tag is specified on a field of type int64, uint, uint32, or uint64 and the value of the field can fit in a signed int32, the field will be serialized as a BSON int32 rather than a BSON int64. For other types, this tag is ignored.
truncate
: If the truncate struct tag is specified on a field with a non-float numeric type, BSON doubles unmarshalled into that field will be trucated at the decimal point. For example, if 3.14 is unmarshalled into a field of type int, it will be unmarshalled as 3. If this tag is not specified, the decoder will throw an error if the value cannot be decoded without losing precision. For float64 or non-numeric types, this tag is ignored.
inline
: If the inline struct tag is specified for a struct or map field, the field will be "flattened" when marshalling and "un-flattened" when unmarshalling. This means that all of the fields in that struct/map will be pulled up one level and will become top-level fields rather than being fields in a nested document. For example, if a map field named "Map" with value map[string]interface{}{"foo": "bar"} is inlined, the resulting document will be {"foo": "bar"} instead of {"map": {"foo": "bar"}}. There can only be one inlined map field in a struct. If there are duplicated fields in the resulting document when an inlined field is marshalled, an error will be returned. This tag can be used with fields that are pointers to structs. If an inlined pointer field is nil, it will not be marshalled. For fields that are not maps or structs, this tag is ignored.
Further, you may use the tag value -
to indicate you want to skip a given field.
If you want to perform additional validation, you may implement the bson.Marshaler
and / or bson.Unmarshaler
interfaces which will be called by mongo-go
whenever a value is marshalled to / unmarshalled from bson.

- 389,944
- 63
- 907
- 827