add 'Validate' method to the 'Config'

This commit is contained in:
2025-09-07 01:59:23 +04:00
parent 896bb3ad2d
commit 8b8f32d58f
4 changed files with 601 additions and 12 deletions

View File

@@ -1,9 +1,11 @@
package config
import (
"errors"
"fmt"
"net/url"
"os"
"slices"
"time"
"github.com/aykhans/dodo/pkg/types"
@@ -29,7 +31,10 @@ var Defaults = struct {
SkipVerify: false,
}
var SupportedProxySchemes = []string{"http", "socks5", "socks5h"}
var (
ValidProxySchemes = []string{"http", "socks5", "socks5h"}
ValidRequestURLSchemes = []string{"http", "https"}
)
type IParser interface {
Parse() (*Config, error)
@@ -120,12 +125,73 @@ func (config *Config) SetDefaults() {
}
}
// Validate validates the config fields.
// It can return the following errors:
// - types.FieldValidationErrors
func (config Config) Validate() error {
validationErrors := make([]types.FieldValidationError, 0)
if config.Method == nil {
validationErrors = append(validationErrors, types.NewFieldValidationError("Method", "", errors.New("method is required")))
}
if config.URL == nil {
validationErrors = append(validationErrors, types.NewFieldValidationError("URL", "", errors.New("URL is required")))
} else if !slices.Contains(ValidRequestURLSchemes, config.URL.Scheme) {
validationErrors = append(validationErrors, types.NewFieldValidationError("URL", config.URL.String(), fmt.Errorf("URL scheme must be one of: %v", ValidRequestURLSchemes)))
}
if config.DodosCount == nil {
validationErrors = append(validationErrors, types.NewFieldValidationError("Dodos Count", "", errors.New("dodos count is required")))
} else if *config.DodosCount == 0 {
validationErrors = append(validationErrors, types.NewFieldValidationError("Dodos Count", "0", errors.New("dodos count must be greater than 0")))
}
switch {
case config.RequestCount == nil && config.Duration == nil:
validationErrors = append(validationErrors, types.NewFieldValidationError("Request Count / Duration", "", errors.New("either request count or duration must be specified")))
case (config.RequestCount != nil && config.Duration != nil) && (*config.RequestCount == 0 && *config.Duration == 0):
validationErrors = append(validationErrors, types.NewFieldValidationError("Request Count / Duration", "0", errors.New("both request count and duration cannot be zero")))
case config.RequestCount != nil && config.Duration == nil && *config.RequestCount == 0:
validationErrors = append(validationErrors, types.NewFieldValidationError("Request Count", "0", errors.New("request count must be greater than 0")))
case config.RequestCount == nil && config.Duration != nil && *config.Duration == 0:
validationErrors = append(validationErrors, types.NewFieldValidationError("Duration", "0", errors.New("duration must be greater than 0")))
}
if config.Yes == nil {
validationErrors = append(validationErrors, types.NewFieldValidationError("Yes", "", errors.New("yes field is required")))
}
if config.SkipVerify == nil {
validationErrors = append(validationErrors, types.NewFieldValidationError("Skip Verify", "", errors.New("skip verify field is required")))
}
for i, proxy := range config.Proxies {
if !slices.Contains(ValidProxySchemes, proxy.Scheme) {
validationErrors = append(
validationErrors,
types.NewFieldValidationError(
fmt.Sprintf("Proxy[%d]", i),
proxy.String(),
fmt.Errorf("proxy scheme must be one of: %v", ValidProxySchemes),
),
)
}
}
if len(validationErrors) > 0 {
return types.NewFieldValidationErrors(validationErrors)
}
return nil
}
func ReadAllConfigs() *Config {
envParser := NewConfigENVParser("DODO")
envConfig, err := envParser.Parse()
_ = utils.HandleErrorOrDie(err,
utils.OnCustomError(func(err types.FieldParseErrors) error {
printValidationErrors("ENV", err.Errors...)
printParseErrors("ENV", err.Errors...)
fmt.Println()
os.Exit(1)
return nil
@@ -148,7 +214,7 @@ func ReadAllConfigs() *Config {
utils.OnCustomError(func(err types.FieldParseErrors) error {
cliParser.PrintHelp()
fmt.Println()
printValidationErrors("CLI", err.Errors...)
printParseErrors("CLI", err.Errors...)
os.Exit(1)
return nil
}),
@@ -169,7 +235,7 @@ func ReadAllConfigs() *Config {
return nil
}),
utils.OnCustomError(func(err types.FieldParseErrors) error {
printValidationErrors(fmt.Sprintf("CONFIG FILE '%s'", configFile.Path()), err.Errors...)
printParseErrors(fmt.Sprintf("CONFIG FILE '%s'", configFile.Path()), err.Errors...)
os.Exit(1)
return nil
}),
@@ -178,6 +244,23 @@ func ReadAllConfigs() *Config {
envConfig.Merge(fileConfig)
}
envConfig.SetDefaults()
err = envConfig.Validate()
_ = utils.HandleErrorOrDie(err,
utils.OnCustomError(func(err types.FieldValidationErrors) error {
for _, fieldErr := range err.Errors {
if fieldErr.Value == "" {
utils.PrintErr(text.FgYellow, "[VALIDATION] Field '%s': %v", fieldErr.Field, fieldErr.Err)
} else {
utils.PrintErr(text.FgYellow, "[VALIDATION] Field '%s' (%s): %v", fieldErr.Field, fieldErr.Value, fieldErr.Err)
}
}
os.Exit(1)
return nil
}),
)
return envConfig
}
@@ -210,7 +293,7 @@ func parseConfigFile(configFile types.ConfigFile, maxDepth int) (*Config, error)
return fileConfig, nil
}
func printValidationErrors(parserName string, errors ...types.FieldParseError) {
func printParseErrors(parserName string, errors ...types.FieldParseError) {
for _, fieldErr := range errors {
if fieldErr.Value == "" {
utils.PrintErr(text.FgYellow, "[%s] Field '%s': %v", parserName, fieldErr.Field, fieldErr.Err)

View File

@@ -553,8 +553,8 @@ func TestParseConfigFile(t *testing.T) {
})
}
func TestPrintValidationErrors(t *testing.T) {
t.Run("printValidationErrors with empty value", func(t *testing.T) {
func TestPrintParseErrors(t *testing.T) {
t.Run("printParseErrors with empty value", func(t *testing.T) {
// This function prints to stdout, so we can't easily test its output
// But we can test that it doesn't panic
errors := []types.FieldParseError{
@@ -567,11 +567,11 @@ func TestPrintValidationErrors(t *testing.T) {
// Should not panic
assert.NotPanics(t, func() {
printValidationErrors("TEST", errors...)
printParseErrors("TEST", errors...)
})
})
t.Run("printValidationErrors with value", func(t *testing.T) {
t.Run("printParseErrors with value", func(t *testing.T) {
errors := []types.FieldParseError{
{
Field: "test_field",
@@ -582,11 +582,11 @@ func TestPrintValidationErrors(t *testing.T) {
// Should not panic
assert.NotPanics(t, func() {
printValidationErrors("TEST", errors...)
printParseErrors("TEST", errors...)
})
})
t.Run("printValidationErrors with multiple errors", func(t *testing.T) {
t.Run("printParseErrors with multiple errors", func(t *testing.T) {
errors := []types.FieldParseError{
{
Field: "field1",
@@ -602,7 +602,345 @@ func TestPrintValidationErrors(t *testing.T) {
// Should not panic
assert.NotPanics(t, func() {
printValidationErrors("TEST", errors...)
printParseErrors("TEST", errors...)
})
})
}
func TestValidate(t *testing.T) {
t.Run("Valid config returns no error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
timeout := 30 * time.Second
duration := 1 * time.Minute
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
Timeout: &timeout,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Duration: &duration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
assert.NoError(t, err)
})
t.Run("Missing Method returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "method is required")
})
t.Run("Missing URL returns validation error", func(t *testing.T) {
config := Config{
Method: utils.ToPtr("GET"),
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "URL is required")
})
t.Run("Invalid URL scheme returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("ftp://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "URL scheme must be one of")
})
t.Run("Missing DodosCount returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "dodos count is required")
})
t.Run("Zero DodosCount returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(0)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "dodos count must be greater than 0")
})
t.Run("Missing both RequestCount and Duration returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "either request count or duration must be specified")
})
t.Run("Both RequestCount and Duration zero returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
duration := time.Duration(0)
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(0)),
Duration: &duration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "both request count and duration cannot be zero")
})
t.Run("Zero RequestCount only returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(0)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "request count must be greater than 0")
})
t.Run("Zero Duration only returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
duration := time.Duration(0)
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
Duration: &duration,
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "duration must be greater than 0")
})
t.Run("Missing Yes returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "yes field is required")
})
t.Run("Missing SkipVerify returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "skip verify field is required")
})
t.Run("Invalid proxy scheme returns validation error", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
proxyURL, _ := url.Parse("ftp://proxy.example.com:8080")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
Proxies: types.Proxies{types.Proxy(*proxyURL)},
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Contains(t, err.Error(), "proxy scheme must be one of")
assert.Contains(t, err.Error(), "Proxy[0]")
})
t.Run("Multiple invalid proxies return validation errors", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
proxyURL1, _ := url.Parse("ftp://proxy1.example.com:8080")
proxyURL2, _ := url.Parse("ldap://proxy2.example.com:389")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
Proxies: types.Proxies{types.Proxy(*proxyURL1), types.Proxy(*proxyURL2)},
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Len(t, validationErr.Errors, 2)
assert.Contains(t, err.Error(), "Proxy[0]")
assert.Contains(t, err.Error(), "Proxy[1]")
})
t.Run("Valid proxy schemes pass validation", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
proxyURL1, _ := url.Parse("http://proxy1.example.com:8080")
proxyURL2, _ := url.Parse("socks5://proxy2.example.com:1080")
proxyURL3, _ := url.Parse("socks5h://proxy3.example.com:1080")
config := Config{
Method: utils.ToPtr("GET"),
URL: testURL,
DodosCount: utils.ToPtr(uint(5)),
RequestCount: utils.ToPtr(uint(100)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
Proxies: types.Proxies{types.Proxy(*proxyURL1), types.Proxy(*proxyURL2), types.Proxy(*proxyURL3)},
}
err := config.Validate()
assert.NoError(t, err)
})
t.Run("Multiple validation errors are collected", func(t *testing.T) {
config := Config{
// Missing Method, URL, DodosCount, Yes, SkipVerify
// Missing both RequestCount and Duration
}
err := config.Validate()
require.Error(t, err)
var validationErr types.FieldValidationErrors
require.ErrorAs(t, err, &validationErr)
assert.Len(t, validationErr.Errors, 6) // All required fields missing
assert.Contains(t, err.Error(), "method is required")
assert.Contains(t, err.Error(), "URL is required")
assert.Contains(t, err.Error(), "dodos count is required")
assert.Contains(t, err.Error(), "either request count or duration must be specified")
assert.Contains(t, err.Error(), "yes field is required")
assert.Contains(t, err.Error(), "skip verify field is required")
})
t.Run("Valid config with Duration only passes validation", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
duration := 30 * time.Second
config := Config{
Method: utils.ToPtr("POST"),
URL: testURL,
DodosCount: utils.ToPtr(uint(10)),
Duration: &duration,
Yes: utils.ToPtr(false),
SkipVerify: utils.ToPtr(true),
}
err := config.Validate()
assert.NoError(t, err)
})
t.Run("Valid config with RequestCount only passes validation", func(t *testing.T) {
testURL, _ := url.Parse("https://example.com")
config := Config{
Method: utils.ToPtr("PUT"),
URL: testURL,
DodosCount: utils.ToPtr(uint(3)),
RequestCount: utils.ToPtr(uint(50)),
Yes: utils.ToPtr(true),
SkipVerify: utils.ToPtr(false),
}
err := config.Validate()
assert.NoError(t, err)
})
}

View File

@@ -63,6 +63,52 @@ func (e FieldParseErrors) Error() string {
return errorString
}
type FieldValidationError struct {
Field string
Value string
Err error
}
func NewFieldValidationError(field string, value string, err error) FieldValidationError {
if err == nil {
err = ErrNoError
}
return FieldValidationError{field, value, err}
}
func (e FieldValidationError) Error() string {
return fmt.Sprintf("Field '%s' validation failed: %v", e.Field, e.Err)
}
func (e FieldValidationError) Unwrap() error {
return e.Err
}
type FieldValidationErrors struct {
Errors []FieldValidationError
}
func NewFieldValidationErrors(fieldValidationErrors []FieldValidationError) FieldValidationErrors {
return FieldValidationErrors{fieldValidationErrors}
}
func (e FieldValidationErrors) Error() string {
if len(e.Errors) == 0 {
return "No field validation errors"
}
if len(e.Errors) == 1 {
return e.Errors[0].Error()
}
errorString := ""
for _, err := range e.Errors {
errorString += err.Error() + "\n"
}
errorString, _ = strings.CutSuffix(errorString, "\n")
return errorString
}
type UnmarshalError struct {
error error
}

View File

@@ -252,6 +252,118 @@ func TestNewUnmarshalError(t *testing.T) {
})
}
func TestFieldValidationError_Error(t *testing.T) {
t.Run("Error returns formatted message", func(t *testing.T) {
originalErr := errors.New("invalid value")
fieldErr := NewFieldValidationError("username", "testuser", originalErr)
expected := "Field 'username' validation failed: invalid value"
assert.Equal(t, expected, fieldErr.Error())
})
t.Run("Error with empty field name", func(t *testing.T) {
originalErr := errors.New("test error")
fieldErr := NewFieldValidationError("", "somevalue", originalErr)
expected := "Field '' validation failed: test error"
assert.Equal(t, expected, fieldErr.Error())
})
t.Run("Error with nil underlying error", func(t *testing.T) {
fieldErr := NewFieldValidationError("field", "value123", nil)
expected := "Field 'field' validation failed: no error (internal)"
assert.Equal(t, expected, fieldErr.Error())
})
}
func TestFieldValidationError_Unwrap(t *testing.T) {
t.Run("Unwrap returns original error", func(t *testing.T) {
originalErr := errors.New("original error")
fieldErr := NewFieldValidationError("field", "value", originalErr)
assert.Equal(t, originalErr, fieldErr.Unwrap())
})
t.Run("Unwrap with nil error", func(t *testing.T) {
fieldErr := NewFieldValidationError("field", "value", nil)
assert.Equal(t, ErrNoError, fieldErr.Unwrap())
})
}
func TestNewFieldValidationError(t *testing.T) {
t.Run("Creates FieldValidationError with correct values", func(t *testing.T) {
originalErr := errors.New("test error")
fieldErr := NewFieldValidationError("testField", "testValue", originalErr)
assert.Equal(t, "testField", fieldErr.Field)
assert.Equal(t, "testValue", fieldErr.Value)
assert.Equal(t, originalErr, fieldErr.Err)
})
t.Run("Creates FieldValidationError with ErrNoError when nil passed", func(t *testing.T) {
fieldErr := NewFieldValidationError("testField", "testValue", nil)
assert.Equal(t, "testField", fieldErr.Field)
assert.Equal(t, "testValue", fieldErr.Value)
assert.Equal(t, ErrNoError, fieldErr.Err)
})
}
func TestFieldValidationErrors_Error(t *testing.T) {
t.Run("Error with no errors returns default message", func(t *testing.T) {
fieldErrors := NewFieldValidationErrors([]FieldValidationError{})
assert.Equal(t, "No field validation errors", fieldErrors.Error())
})
t.Run("Error with single error returns single error message", func(t *testing.T) {
fieldErr := NewFieldValidationError("field1", "value1", errors.New("error1"))
fieldErrors := NewFieldValidationErrors([]FieldValidationError{fieldErr})
expected := "Field 'field1' validation failed: error1"
assert.Equal(t, expected, fieldErrors.Error())
})
t.Run("Error with multiple errors returns concatenated messages", func(t *testing.T) {
fieldErr1 := NewFieldValidationError("field1", "value1", errors.New("error1"))
fieldErr2 := NewFieldValidationError("field2", "value2", errors.New("error2"))
fieldErr3 := NewFieldValidationError("field3", "value3", errors.New("error3"))
fieldErrors := NewFieldValidationErrors([]FieldValidationError{fieldErr1, fieldErr2, fieldErr3})
expected := "Field 'field1' validation failed: error1\nField 'field2' validation failed: error2\nField 'field3' validation failed: error3"
assert.Equal(t, expected, fieldErrors.Error())
})
t.Run("Error with two errors", func(t *testing.T) {
fieldErr1 := NewFieldValidationError("username", "john", errors.New("too short"))
fieldErr2 := NewFieldValidationError("email", "invalid", errors.New("invalid format"))
fieldErrors := NewFieldValidationErrors([]FieldValidationError{fieldErr1, fieldErr2})
expected := "Field 'username' validation failed: too short\nField 'email' validation failed: invalid format"
assert.Equal(t, expected, fieldErrors.Error())
})
}
func TestNewFieldValidationErrors(t *testing.T) {
t.Run("Creates FieldValidationErrors with correct values", func(t *testing.T) {
fieldErr1 := NewFieldValidationError("field1", "value1", errors.New("error1"))
fieldErr2 := NewFieldValidationError("field2", "value2", errors.New("error2"))
fieldErrors := NewFieldValidationErrors([]FieldValidationError{fieldErr1, fieldErr2})
assert.Len(t, fieldErrors.Errors, 2)
assert.Equal(t, fieldErr1, fieldErrors.Errors[0])
assert.Equal(t, fieldErr2, fieldErrors.Errors[1])
})
t.Run("Creates FieldValidationErrors with empty slice", func(t *testing.T) {
fieldErrors := NewFieldValidationErrors([]FieldValidationError{})
assert.Empty(t, fieldErrors.Errors)
})
}
func TestErrorImplementsErrorInterface(t *testing.T) {
t.Run("FieldParseError implements error interface", func(t *testing.T) {
var err error = NewFieldParseError("field", "value", errors.New("test"))
@@ -277,4 +389,14 @@ func TestErrorImplementsErrorInterface(t *testing.T) {
var err error = NewUnmarshalError(errors.New("test"))
assert.Error(t, err)
})
t.Run("FieldValidationError implements error interface", func(t *testing.T) {
var err error = NewFieldValidationError("field", "value", errors.New("test"))
assert.Error(t, err)
})
t.Run("FieldValidationErrors implements error interface", func(t *testing.T) {
var err error = NewFieldValidationErrors([]FieldValidationError{})
assert.Error(t, err)
})
}