Firevault is a Firestore ODM for Go, providing robust data modelling and document handling.
Use go get to install Firevault.
go get github.com/bobch27/firevault_go
Import the package in your code.
import "github.com/bobch27/firevault_go"
You can connect to Firevault using the Connect
method, providing a project ID. A Firevault Connection
is designed to be thread-safe and used as a singleton instance. A cache is used under the hood to store struct validation metadata, parsing validation tags once per struct type. Using multiple instances defeats the purpose of caching.
import (
"log"
"github.com/bobch27/firevault_go"
)
// Sets your Google Cloud Platform project ID.
projectID := "YOUR_PROJECT_ID"
ctx := context.Background()
connection, err := firevault.Connect(ctx, projectID)
if err != nil {
log.Fatalln("Firevault initialisation failed:", err)
}
To close the connection, when it's no longer needed, you can call the Close
method. It need not be called at program exit.
defer connection.Close()
Defining a model is as simple as creating a struct with Firevault tags.
type User struct {
Name string `firevault:"name,required,omitempty"`
Email string `firevault:"email,required,email,is_unique,omitempty"`
Password string `firevault:"password,required,min=6,transform=hash_pass,omitempty"`
Address *Address `firevault:"address,omitempty"`
Age int `firevault:"age,required,min=18,omitempty"`
}
type Address struct {
Line1 string `firevault:",omitempty"`
City string `firevault:"-"`
}
When defining a new struct type with a Firevault tag, note that the rules' order matters (apart from the different omitempty
rules, which can be used anywhere).
The first rule is always the field name which will be used in Firestore. You can skip that by just using a comma, before adding the others.
After that, each rule is a different validation, and they will be parsed in order.
Other than the validation rules, Firevault supports the following built-in ones:
omitempty
- If the field is set to it’s default value (e.g.0
forint
, or""
forstring
), the field will be omitted from validation and Firestore.omitempty_create
- Works the same way asomitempty
, but only for theCreate
method. Ignored duringUpdate
andValidate
methods.omitempty_update
- Works the same way asomitempty
, but only for theUpdate
method. Ignored duringCreate
andValidate
methods.omitempty_validate
- Works the same way asomitempty
, but only for theValidate
method. Ignored duringCreate
andUpdate
methods.dive
- If the field is an array/slice or a map, this rule allows to recursively loop through and validate inner fields. Useful when the inner fields are structs with custom validation tags. Ignored for fields that are not arrays/slices or maps.-
- Ignores the field.
Firevault validates fields' values based on the defined rules. There are built-in validations, with support for adding custom ones.
Again, the order in which they are executed depends on the tag order.
Built-in validations:
required
- Validates whether the field's value is not the default type value (i.e.nil
forpointer
,""
forstring
,0
forint
etc.). Fails when it is the default.required_create
- Works the same way asrequired
, but only for theCreate
method. Ignored duringUpdate
andValidate
methods.required_update
- Works the same way asrequired
, but only for theUpdate
method. Ignored duringCreate
andValidate
methods.required_validate
- Works the same way asrequired
, but only for theValidate
method. Ignored duringCreate
andUpdate
methods.max
- Validates whether the field's value, or length, is less than or equal to the param's value. Requires a param (e.g.max=20
). For numbers, it checks the value, for strings, maps and slices, it checks the length.min
- Validates whether the field's value, or length, is greater than or equal to the param's value. Requires a param (e.g.min=20
). For numbers, it checks the value, for strings, maps and slices, it checks the length.email
- Validates whether the field's string value is a valid email address.
Custom validations:
- To define a custom validation, use
Connection
'sRegisterValidation
method.- Expects:
- name: A
string
defining the validation name. If the name includes a method-specific suffix ("_create", "_update", or "_validate"), the rule will be applied exclusively during calls to the corresponding method type and ignored for others. - func: A function of type
ValidationFn
. The passed in function accepts two parameters.- Expects:
- ctx: A context.
- fs: A value that implements the
FieldScope
interface, which gives access to different field data, useful during the validation. Available methods forFieldScope
can be found in thefield_scope.go
file.
- Returns:
- result: A
bool
which returnstrue
if check has passed, andfalse
if it hasn't. - error: An
error
in case something went wrong during the check.
- result: A
- Expects:
- runOnNil (optional): An optional
bool
indicating whether the validation should be executed on nil values. The default isfalse
.
- name: A
- Expects:
Registering custom validations is not thread-safe. It is intended that all rules be registered, prior to any validation. Also, if a rule with the same name already exists, the previous one will be replaced.
connection.RegisterValidation(
"is_upper",
func(_ context.Context, fs FieldScope) (bool, error) {
if fs.Kind() != reflect.String {
return false, nil
}
s := fs.Value().String()
return s == strings.toUpper(s), nil
},
)
You can then chain the rule like a normal one.
type User struct {
Name string `firevault:"name,required,is_upper,omitempty"`
}
Firevault also supports rules that transform the field's value. There are built-in transformations, with support for adding custom ones. To use them, it's as simple as adding a prefix to the rule.
Again, the order in which they are executed depends on the tag order.
Built-in transformations:
uppercase
- Converts the field's string value to upper case. If the field is not a string, it simply returns its original value and no error.lowercase
- Converts the field's string value to lower case. If the field is not a string, it simply returns its original value and no error.trim_space
- Removes all leading and trailing white space around the field's string value. If the field is not a string, it simply returns its original value and no error.
Custom transformations:
- To define a transformation, use
Connection
'sRegisterTransformation
method.- Expects:
- name: A
string
defining the transformation name. If the name includes a method-specific suffix ("_create", "_update", or "_validate"), the rule will be applied exclusively during calls to the corresponding method type and ignored for others. - func: A function of type
TransformationFn
. The passed in function accepts two parameters.- Expects:
- ctx: A context.
- fs: A value that implements the
FieldScope
interface, which gives access to different field data, useful during the transformation. Available methods forFieldScope
can be found in thefield_scope.go
file.
- Returns:
- result: An
interface{}
with the new, transformed, value. - error: An
error
in case something went wrong during the transformation.
- result: An
- Expects:
- runOnNil (optional): An optional
bool
indicating whether the transformation should be executed on nil values. The default isfalse
.
- name: A
- Expects:
Registering custom transformations is not thread-safe. It is intended that all rules be registered, prior to any validation. Also, if a rule with the same name already exists, the previous one will be replaced.
connection.RegisterTransformation(
"to_lower",
func(_ context.Context, fs FieldScope) (interface{}, error) {
if fs.Kind() != reflect.String {
return fs.Value().Interface(), errors.New(fs.StructField() + " must be a string")
}
return strings.ToLower(fs.Value().String()), nil
},
)
You can then chain the rule like a normal one, but don't forget to use the transform=
prefix.
Again, the tag order matters. Defining a transformation at the end, means the value will be updated after the validations, whereas a definition at the start, means the field will be updated and then validated.
type User struct {
// transformation will take place after all validations have passed
Email string `firevault:"email,required,email,transform=to_lower,omitempty"`
}
type User struct {
// the "email" validation will be executed on the new value
Email string `firevault:"email,required,transform=to_lower,email,omitempty"`
}
A Firevault CollectionRef
instance allows for interacting with Firestore, through various read and write methods.
These instances are lightweight and safe to create repeatedly. They can be freely used as needed, without concern for maintaining a singleton instance, as each instance independently references the specified Firestore collection.
To create a CollectionRef
instance, call the Collection
function, using the struct type parameter, and passing in the Connection
instance, as well as a collection path.
collection := firevault.Collection[User](connection, "users")
The CollectionRef
instance has 7 built-in methods to support interaction with Firestore.
Create
- A method which validates passed in data and adds it as a document to Firestore.- Expects:
- ctx: A context.
- data: A
pointer
of astruct
with populated fields which will be added to Firestore after validation. - options (optional): An instance of
Options
with the following chainable methods having an effect.- SkipValidation: When used, it means all validation tags will be ingored (the
name
andomitempty
rules will be acknowledged). - ID: When invoked with a
string
param, that value will be used as an ID when adding the document to Firestore. If not used, or called with no params, an auto generated ID will be used. - AllowEmptyFields: When invoked with a variable number of
string
params, the fields that match the provided field paths will ignore theomitempty
andomitempty_create
rules. This can be useful when a field must be set to its zero value only on certain method calls. If not used, or called with no params, all fields will honour the two rules. - ModifyOriginal: When used, if there are transformations which alter field values, the original, passed in struct data will also be updated in place. Note: when used, this will make the entire method call thread-unsafe, so should be used with caution.
- SkipValidation: When used, it means all validation tags will be ingored (the
- Returns:
- id: A
string
with the new document's ID. - error: An
error
in case something goes wrong during validation or interaction with Firestore.
- id: A
- Expects:
user := User{
Name: "Bobby Donev",
Email: "[email protected]",
Password: "12356",
Age: 26,
Address: &Address{
Line1: "1 High Street",
City: "London",
},
}
id, err := collection.Create(ctx, &user)
if err != nil {
fmt.Println(err)
}
fmt.Println(id) // "6QVHL46WCE680ZG2Xn3X"
id, err := collection.Create(
ctx,
&user,
NewOptions().CustomID("custom-id"),
)
if err != nil {
fmt.Println(err)
}
fmt.Println(id) // "custom-id"
user := User{
Name: "Bobby Donev",
Email: "[email protected]",
Password: "12356",
Age: 0,
Address: &Address{
Line1: "1 High Street",
City: "London",
},
}
id, err := collection.Create(
ctx,
&user,
NewOptions().AllowEmptyFields("age"),
)
if err != nil {
fmt.Println(err)
}
fmt.Println(id) // "6QVHL46WCE680ZG2Xn3X"
Update
- A method which validates passed in data and updates all Firestore documents which match providedQuery
. IfQuery
contains anID
clause and no documents are matched, new ones will be created for each provided ID. The method uses Firestore'sBulkWriter
under the hood, meaning the operation is not atomic.- Expects:
- ctx: A context.
- query: A
Query
instance to filter which documents to update. - data: A
pointer
of astruct
with populated fields which will be used to update the documents after validation. - options (optional): An instance of
Options
with the following chainable methods having an effect.- SkipValidation: When used, it means all validation tags will be ingored (the
name
andomitempty
rules will be acknowledged). - MergeFields: When invoked with a variable number of
string
params, the fields which match the provided field paths will be overwritten. Other fields on the document will be untouched. If not used, or called with no params, all the fields given in the data argument will be overwritten. If a field is specified, but is not present in the data passed, the field will be deleted from the document (usingfirestore.Delete
). - AllowEmptyFields: When invoked with a variable number of
string
params, the fields that match the provided field paths will ignore theomitempty
andomitempty_update
rules. This can be useful when a field must be set to its zero value only on certain method calls. If not used, or called with no params, all fields will honour the two rules. - ModifyOriginal: When used, if there are transformations which alter field values, the original, passed in struct data will also be updated in place. Note: when used, this will make the entire method call thread-unsafe, so should be used with caution.
- SkipValidation: When used, it means all validation tags will be ingored (the
- Returns:
- error: An
error
in case something goes wrong during validation or interaction with Firestore.
- error: An
- Important:
- If neither
omitempty
, noromitempty_update
rules have been used, non-specified field values in the passed in data will be set to Go's default values, thus updating all document fields. To prevent that behaviour, please use one of the two rules. - If no documents match the provided
Query
, the operation will do nothing and will not return an error.
- If neither
- Expects:
user := User{
Password: "123567",
}
err := collection.Update(
ctx,
NewQuery().ID("6QVHL46WCE680ZG2Xn3X"),
&user,
)
if err != nil {
fmt.Println(err)
}
fmt.Println("Success")
user := User{
Password: "123567",
}
err := collection.Update(
ctx,
NewQuery().ID("6QVHL46WCE680ZG2Xn3X"),
&user,
NewOptions().SkipValidation(),
)
if err != nil {
fmt.Println(err)
}
fmt.Println("Success")
user := User{
Address: &Address{
Line1: "1 Main Road",
City: "New York",
}
}
err := collection.Update(
ctx,
NewQuery().ID("6QVHL46WCE680ZG2Xn3X"),
&user,
NewOptions().MergeFields("address.Line1"),
)
if err != nil {
fmt.Println(err)
}
fmt.Println("Success") // only the address.Line1 field will be updated
user := User{
Address: &Address{
City: "New York",
}
}
err := collection.Update(
ctx,
NewQuery().ID("6QVHL46WCE680ZG2Xn3X"),
&user,
NewOptions().MergeFields("address.Line1"),
)
if err != nil {
fmt.Println(err)
}
fmt.Println("Success") // address.Line1 field will be deleted from document, since it's not present in data
Validate
- A method which validates and transforms passed in data.- Expects:
- ctx: A context.
- data: A
pointer
of astruct
with populated fields which will be validated. - options (optional): An instance of
Options
with the following chainable methods having an effect.- SkipValidation: When used, it means all validation tags will be ingored (the
name
andomitempty
rules will be acknowledged). - AllowEmptyFields: When invoked with a variable number of
string
params, the fields that match the provided field paths will ignore theomitempty
andomitempty_validate
rules. This can be useful when a field must be set to its zero value only on certain method calls. If not used, or called with no params, all fields will honour the two rules. - ModifyOriginal: When used, if there are transformations which alter field values, the original, passed in struct data will also be updated in place. Note: when used, this will make the entire method call thread-unsafe, so should be used with caution.
- SkipValidation: When used, it means all validation tags will be ingored (the
- Returns:
- error: An
error
in case something goes wrong during validation.
- error: An
- Important:
- If neither
omitempty
, noromitempty_validate
rules have been used, non-specified field values in the passed in data will be set to Go's default values.
- If neither
- Expects:
user := User{
Email: "[email protected]",
}
err := collection.Validate(ctx, &user)
if err != nil {
fmt.Println(err)
}
fmt.Println(user) // {[email protected]}
Delete
- A method which deletes all Firestore documents which match providedQuery
. The method uses Firestore'sBulkWriter
under the hood, meaning the operation is not atomic.- Expects:
- ctx: A context.
- query: A
Query
instance to filter which documents to delete.
- Returns:
- error: An
error
in case something goes wrong during interaction with Firestore.
- error: An
- If no documents match the provided
Query
, the method does nothing anderror
isnil
.
- Expects:
err := collection.Delete(
ctx,
NewQuery().ID("6QVHL46WCE680ZG2Xn3X"),
)
if err != nil {
fmt.Println(err)
}
fmt.Println("Success")
Find
- A method which gets the Firestore documents which match the provided query.- Expects:
- ctx: A context.
- query: A
Query
to filter and order documents.
- Returns:
- docs: A
slice
containing the results of typeDocument[T]
(whereT
is the type used when initiating the collection instance).Document[T]
has two properties.- ID: A
string
which holds the document's ID. - Data: The document's data of type
T
.
- ID: A
- error: An
error
in case something goes wrong during interaction with Firestore.
- docs: A
- Expects:
users, err := collection.Find(
ctx,
NewQuery().
Where("email", "==", "hello@bobbydonev").
Limit(1),
)
if err != nil {
fmt.Println(err)
}
fmt.Println(users) // []Document[User]
fmt.Println(users[0].ID) // 6QVHL46WCE680ZG2Xn3X
FindOne
- A method which gets the first Firestore document which matches the providedQuery
. Returns an empty Document[T] (empty ID string and zero-value T Data), and no error if no documents are found.- Expects:
- ctx: A context.
- query: A
Query
to filter and order documents.
- Returns:
- doc: Returns the document with type
Document[T]
(whereT
is the type used when initiating the collection instance).Document[T]
has two properties.- ID: A
string
which holds the document's ID. - Data: The document's data of type
T
.
- ID: A
- error: An
error
in case something goes wrong during interaction with Firestore.
- doc: Returns the document with type
- Expects:
user, err := collection.FindOne(
ctx,
NewQuery().ID("6QVHL46WCE680ZG2Xn3X"),
)
if err != nil {
fmt.Println(err)
}
fmt.Println(user.Data) // {Bobby Donev [email protected] asdasdkjahdks 26 0xc0001d05a0}
Count
- A method which gets the number of Firestore documents which match the provided query.- Expects:
- ctx: A context.
- query: An instance of
Query
to filter documents.
- Returns:
- count: An
int64
representing the number of documents which meet the criteria. - error: An
error
in case something goes wrong during interaction with Firestore.
- count: An
- Expects:
count, err := collection.Count(
ctx,
NewQuery().Where("email", "==", "hello@bobbydonev"),
)
if err != nil {
fmt.Println(err)
}
fmt.Println(count) // 1
A Firevault Query
instance allows querying Firestore, by chaining various methods. The query can have multiple filters.
To create a Query
instance, call the NewQuery
method.
query := firevault.NewQuery()
The Query
instance has 10 built-in methods to support filtering and ordering Firestore documents.
ID
- Returns a newQuery
that that exclusively filters the set of results based on provided IDs.- Expects:
- ids: A varying number of
string
values used to filter out results.
- ids: A varying number of
- Returns:
- A new
Query
instance.
- A new
- Important:
- ID takes precedence over and completely overrides any previous or subsequent calls to other Query methods, including Where. To filter by ID as well as other criteria, use the Where method with the special DocumentID field, instead of calling ID.
- Expects:
newQuery := query.ID("6QVHL46WCE680ZG2Xn3X")
Where
- Returns a newQuery
that filters the set of results.- Expects:
- path: A
string
which can be a single field or a dot-separated sequence of fields. - operator: A
string
which must be one of==
,!=
,<
,<=
,>
,>=
,array-contains
,array-contains-any
,in
ornot-in
. - value: An
interface{}
value used to filter out the results.
- path: A
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev")
OrderBy
- Returns a newQuery
that specifies the order in which results are returned.- Expects:
- path: A
string
which can be a single field or a dot-separated sequence of fields. To order by document name, use the special field pathDocumentID
. - direction: A
Direction
used to specify whether results are returned in ascending or descending order.
- path: A
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").OrderBy("age", Asc)
Limit
- Returns a newQuery
that specifies the maximum number of first results to return.- Expects:
- num: An
int
which indicates the max number of results to return.
- num: An
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").Limit(1)
LimitToLast
- Returns a newQuery
that specifies the maximum number of last results to return.- Expects:
- num: An
int
which indicates the max number of results to return.
- num: An
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").LimitToLast(1)
Offset
- Returns a newQuery
that specifies the number of initial results to skip.- Expects:
- num: An
int
which indicates the number of results to skip.
- num: An
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").Offset(1)
StartAt
- Returns a newQuery
that specifies that results should start at the document with the given field values. Should be called with one field value for each OrderBy clause, in the order that they appear.- Expects:
- values: A varying number of
interface{}
values used to filter out results.
- values: A varying number of
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").OrderBy("age", Asc).StartAt(25)
StartAfter
- Returns a newQuery
that specifies that results should start just after the document with the given field values. Should be called with one field value for each OrderBy clause, in the order that they appear.- Expects:
- values: A varying number of
interface{}
values used to filter out results.
- values: A varying number of
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").OrderBy("age", Asc).StartAfter(25)
EndBefore
- Returns a newQuery
that specifies that results should end just before the document with the given field values. Should be called with one field value for each OrderBy clause, in the order that they appear.- Expects:
- values: A varying number of
interface{}
values used to filter out results.
- values: A varying number of
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").OrderBy("age", Asc).EndBefore(25)
EndAt
- Returns a newQuery
that specifies that results should end at the document with the given field values. Should be called with one field value for each OrderBy clause, in the order that they appear.- Expects:
- values: A varying number of
interface{}
values used to filter out results.
- values: A varying number of
- Returns:
- A new
Query
instance.
- A new
- Expects:
newQuery := query.Where("name", "==", "Bobby Donev").OrderBy("age", Asc).EndAt(25)
A Firevault Options
instance allows for the overriding of default options for validation, creation and updating methods, by chaining various methods.
To create a new Options
instance, call the NewOptions
method.
options := firevault.NewOptions()
The Options
instance has 4 built-in methods to support overriding default CollectionRef
method options.
SkipValidation
- Returns a newOptions
instance that allows to skip the data validation during creation, updating and validation methods. The "name" rule, "omitempty" rules and "ignore" rule will still be honoured.- Returns:
- A new
Options
instance.
- A new
- Returns:
newOptions := options.SkipValidation()
AllowEmptyFields
- Returns a newOptions
instance that allows to specify which field paths should ignore the "omitempty" rules. This can be useful when zero values are needed only during a specific method call. If left empty, those rules will be honoured for all fields.- Expects:
- path: A varying number of
string
values (using dot separation) used to select field paths.
- path: A varying number of
- Returns:
- A new
Options
instance.
- A new
- Expects:
newOptions := options.AllowEmptyFields("age")
ModifyOriginal
- Returns a newOptions
instance that allows the updating of field values in the original passed in data struct after transformations. Note, this will make the operation thread-unsafe, so should be used with caution.- Expects:
- path: A varying number of
string
values (using dot separation) used to select field paths.
- path: A varying number of
- Returns:
- A new
Options
instance.
- A new
- Expects:
newOptions := options.ModifyOriginal()
MergeFields
- Returns a newOptions
instance that allows to specify which field paths to be overwritten. Other fields on the existing document will be untouched. If a provided field path does not refer to a value in the data passed, that field will be deleted from the document. Only used for updating method.- Expects:
- path: A varying number of
string
values (using dot separation) used to select field paths.
- path: A varying number of
- Returns:
- A new
Options
instance.
- A new
- Expects:
newOptions := options.MergeFields("address.Line1")
CustomID
- Returns a newOptions
instance that allows to specify a custom document ID to be used when creating a Firestore document. Only used for creation method.- Expects:
- id: A
string
specifying the custom ID.
- id: A
- Returns:
- A new
Options
instance.
- A new
- Expects:
newOptions := options.CustomID("custom-id")
During collection methods which require validation (i.e. Create
, Update
and Validate
), Firevault may return an error that implements the FieldError
interface, which can aid in presenting custom error messages to users. All other errors are of the usual error
type, and do not satisfy the the interface. Available methods for FieldError
can be found in the field_error.go
file.
Firevault supports the creation of custom, user-friendly, error messages, through ErrorFormatterFn
. These are run whenever a FieldError
is created (i.e. whenever a validation rule fails). All registered formatters are executed on the FieldError
and if all return a nil error (or there's no registered formatters), a FieldError
is returned instead. Otherwise, the first custom error is returned.
Error formatters:
- To define an error formatter, use
Connection
'sRegisterErrorFormatter
method.- Expects:
- func: A function of type
ErrorFormatterFn
. The passed in function accepts one parameter.- Expects:
- fe: An error that complies with the
FieldError
interface.
- fe: An error that complies with the
- Returns:
- error: An
error
with a custom message, usingFieldError
's field validation details.
- error: An
- Expects:
- func: A function of type
- Expects:
Registering custom error formatters is not thread-safe. It is intended that all functions be registered, prior to any validation.
connection.RegisterErrorFormatter(func(fe FieldError) error {
if err.Rule() == "min" {
return fmt.Errorf("%s must be at least %s characters long.", fe.DisplayField(), fe.Param())
}
return nil
})
This is how it can be used.
id, err := collection.Create(ctx, &User{
Name: "Bobby Donev",
Email: "[email protected]",
Password: "12345",
Age: 26,
Address: &Address{
Line1: "1 High Street",
City: "London",
},
})
if err != nil {
fmt.Println(err) // "Password must be at least 6 characters long."
} else {
fmt.Println(id)
}
id, err := collection.Create(ctx, &User{
Name: "Bobby Donev",
Email: "[email protected]", // will fail on the "email" rule
Password: "123456",
Age: 25,
Address: &Address{
Line1: "1 High Street",
City: "London",
},
})
if err != nil {
fmt.Println(err) // error isn't catched by formatter; complies with FieldError
} else {
fmt.Println(id)
}
You can also directly handle and parse returned FieldError
, without registering an error formatter. Here is an example of parsing a returned FieldError
, in cases where an error formatter doesn't catch it, or in cases where no formatters are registered.
func parseError(fe FieldError) {
if fe.StructField() == "Password" { // or fe.Field() == "password"
if fe.Rule() == "min" {
fmt.Printf("Password must be at least %s characters long.", fe.Param())
} else {
fmt.Println(fe.Error())
}
} else {
fmt.Println(fe.Error())
}
}
id, err := collection.Create(ctx, &User{
Name: "Bobby Donev",
Email: "[email protected]",
Password: "12345",
Age: 26,
Address: &Address{
Line1: "1 High Street",
City: "London",
},
})
if err != nil {
var fErr FieldError
if errors.As(err, &fErr) {
parseError(fErr) // "Password must be at least 6 characters long."
} else {
fmt.Println(err.Error())
}
} else {
fmt.Println(id)
}
Firevault's built-in validation is designed to be both robust and efficient. Benchmarks indicate that it performs comparably to industry-leading libraries like go-playground/validator, both with and without caching.
For detailed benchmark results, see BENCHMARKS.md.
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.