Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
12
pkg/validator/can_id.go
Normal file
12
pkg/validator/can_id.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func validateCANID(fl validator.FieldLevel) bool {
|
||||
ok, _ := regexp.Match(`^[0-9]+(-[0-9]+)?$`, []byte(fl.Field().String()))
|
||||
return ok
|
||||
}
|
||||
59
pkg/validator/can_id_test.go
Normal file
59
pkg/validator/can_id_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
)
|
||||
|
||||
type TestCANID struct {
|
||||
Name string `validate:"can_id"`
|
||||
Expected string
|
||||
}
|
||||
|
||||
var canIDValidatorValidTests = []TestCANID{
|
||||
{Name: "1"},
|
||||
{Name: "123"},
|
||||
{Name: "123-456"},
|
||||
}
|
||||
|
||||
func TestValidateCANID(t *testing.T) {
|
||||
var tests = []TestCANID{
|
||||
{
|
||||
Name: "",
|
||||
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
|
||||
},
|
||||
{
|
||||
Name: "-",
|
||||
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
|
||||
},
|
||||
{
|
||||
Name: "-123",
|
||||
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
|
||||
},
|
||||
{
|
||||
Name: "abc",
|
||||
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
|
||||
},
|
||||
{
|
||||
Name: "ab12",
|
||||
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
|
||||
},
|
||||
{
|
||||
Name: "123-123-123",
|
||||
Expected: "Key: 'TestCANID.Name' Error:Field validation for 'Name' failed on the 'can_id' tag",
|
||||
},
|
||||
}
|
||||
|
||||
tests = append(tests, canIDValidatorValidTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.ValidateStruct(test)
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
26
pkg/validator/certificate_serial.go
Normal file
26
pkg/validator/certificate_serial.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"fiskerinc.com/modules/logger"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func validateCertSerial(fl validator.FieldLevel) bool {
|
||||
ok, err := ValidateCertSerialSimple(fl.Field().String())
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("Unable to validate certificate serial number")
|
||||
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func ValidateCertSerialSimple(serial string) (bool, error) {
|
||||
matched, err := regexp.Match(`^([a-zA-Z0-9]{2}[:-]{1}){18,19}[a-zA-Z0-9]{2}$`, []byte(serial))
|
||||
if err != nil {
|
||||
return matched, errors.WithStack(err)
|
||||
}
|
||||
return matched, nil
|
||||
}
|
||||
51
pkg/validator/certificate_serial_test.go
Normal file
51
pkg/validator/certificate_serial_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
)
|
||||
|
||||
type TestCertSerial struct {
|
||||
Name string `validate:"serial"`
|
||||
Expected string
|
||||
}
|
||||
|
||||
var serialValidTests = []TestCertSerial{
|
||||
{Name: "01:01:7c:d2:d0:6b:ff:30:c5:66:9a:0e:dd:19:0a:61:7d:ca:95:33"},
|
||||
{Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed:7d:a1:b9:c0:e1:3a"},
|
||||
{Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed:7d:a1:b9:c0:e1"},
|
||||
}
|
||||
|
||||
func TestValidateSerial(t *testing.T) {
|
||||
var tests = []TestCertSerial{
|
||||
{
|
||||
Name: "",
|
||||
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
|
||||
},
|
||||
{
|
||||
Name: "testing123",
|
||||
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
|
||||
},
|
||||
{
|
||||
Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed:7d:a1:b9:c0",
|
||||
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
|
||||
},
|
||||
{
|
||||
Name: "01:03:68:f1:b7:1f:e1:27:90:01:9c:b0:0d:ed7da1b9c0e13a",
|
||||
Expected: "Key: 'TestCertSerial.Name' Error:Field validation for 'Name' failed on the 'serial' tag",
|
||||
},
|
||||
}
|
||||
|
||||
tests = append(tests, serialValidTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.ValidateStruct(test)
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
31
pkg/validator/dates.go
Normal file
31
pkg/validator/dates.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
const iso8601DateRegexString = "^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d{1,9})?(?:Z|[+-][01]\\d:[0-5]\\d)$"
|
||||
const yyyyMMDDDateRegexString = `^\d{4}\-\d{2}\-\d{2}$`
|
||||
|
||||
var iso8601DateRegex = regexp.MustCompile(iso8601DateRegexString)
|
||||
var yyyyMMDDDateRegex = regexp.MustCompile(yyyyMMDDDateRegexString)
|
||||
|
||||
func IsISO8601Date(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
// skip if blank. if required, add required tag
|
||||
if len(value) == 0 {
|
||||
return true
|
||||
}
|
||||
return iso8601DateRegex.MatchString(value)
|
||||
}
|
||||
|
||||
func IsDateYYYYMMDD(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
// skip if blank. if required, add required tag
|
||||
if len(value) == 0 {
|
||||
return true
|
||||
}
|
||||
return yyyyMMDDDateRegex.MatchString(value)
|
||||
}
|
||||
12
pkg/validator/ecu.go
Normal file
12
pkg/validator/ecu.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func validateECU(fl validator.FieldLevel) bool {
|
||||
ecu := fl.Field().String()
|
||||
_, ok := common.EcuMap[ecu]
|
||||
return ok
|
||||
}
|
||||
55
pkg/validator/ecu_test.go
Normal file
55
pkg/validator/ecu_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
)
|
||||
|
||||
type TestECU struct {
|
||||
Name string `validate:"ecu"`
|
||||
Expected string
|
||||
}
|
||||
|
||||
var ecuValidTests = []TestECU{
|
||||
{Name: "BCM"},
|
||||
{Name: "TBOX"},
|
||||
{Name: "iBooster"},
|
||||
}
|
||||
|
||||
func TestValidateECU(t *testing.T) {
|
||||
var tests = []TestECU{
|
||||
{
|
||||
Name: "",
|
||||
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
|
||||
},
|
||||
{
|
||||
Name: "-",
|
||||
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
|
||||
},
|
||||
{
|
||||
Name: "-123",
|
||||
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
|
||||
},
|
||||
{
|
||||
Name: "abc",
|
||||
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
|
||||
},
|
||||
{
|
||||
Name: "ab12",
|
||||
Expected: "Key: 'TestECU.Name' Error:Field validation for 'Name' failed on the 'ecu' tag",
|
||||
},
|
||||
}
|
||||
|
||||
tests = append(tests, ecuValidTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.ValidateStruct(test)
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
15
pkg/validator/email.go
Normal file
15
pkg/validator/email.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func validateEmail(fl validator.FieldLevel) bool {
|
||||
s := fl.Field().String()
|
||||
isEmail, _ := regexp.Match(`^\w([\w\-_+]*(\.[\w\-_+]+)?)*@(\w+[\-.])+\w{2,}$`, []byte(s))
|
||||
hasValidSize, _ := regexp.Match(`^.{0,64}@.{0,63}$`, []byte(s))
|
||||
|
||||
return isEmail && hasValidSize
|
||||
}
|
||||
60
pkg/validator/email_test.go
Normal file
60
pkg/validator/email_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/validator"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_validateEmail(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
emailsList []string
|
||||
expErr error
|
||||
}{
|
||||
"valid": {
|
||||
emailsList: []string{
|
||||
"simple@example.com",
|
||||
"very.common@example.com",
|
||||
"disposable.style.email.with+symbol@example.com",
|
||||
"other.email-with-hyphen@example.com",
|
||||
"fully-qualified-domain@example.com",
|
||||
"user.name+tag+sorting@example.com",
|
||||
"x@example.com",
|
||||
"example-indeed@strange-example.com",
|
||||
"example@s.example",
|
||||
"user-@example.org",
|
||||
"2abc.d@mail.com",
|
||||
},
|
||||
},
|
||||
"invalid": {
|
||||
emailsList: []string{
|
||||
"1234567890123456789012345678901234567890123456789012345678901234+x@example.com",
|
||||
"abc.@mail.com",
|
||||
"abc..def@mail.com",
|
||||
`"".abc@mail.com`,
|
||||
"abc#def@mail.com",
|
||||
"abc.def@mail.c",
|
||||
"abc.def@mail#archive.com",
|
||||
"abc.def@mail",
|
||||
"abc.def@mail..com",
|
||||
},
|
||||
expErr: errors.New("Key: '' Error:Field validation for '' failed on the 'email' tag"),
|
||||
},
|
||||
}
|
||||
v := validator.GetValidator()
|
||||
for tname, tt := range tests {
|
||||
t.Run(tname, func(t *testing.T) {
|
||||
for _, email := range tt.emailsList {
|
||||
err := v.Var(email, "email")
|
||||
if err != nil && tt.expErr != nil {
|
||||
assert.Equal(t, tt.expErr.Error(), err.Error())
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, tt.expErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
67
pkg/validator/error.go
Normal file
67
pkg/validator/error.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func GetValidationErrorMsg(err error) (bool, string) {
|
||||
valerr, ok := err.(validator.ValidationErrors)
|
||||
if ok {
|
||||
return true, GetError(&valerr)
|
||||
}
|
||||
|
||||
fielderr, ok := err.(*FieldError)
|
||||
if ok {
|
||||
return true, fielderr.Error()
|
||||
}
|
||||
|
||||
pgerr, ok := err.(pg.Error)
|
||||
if ok && pgerr.IntegrityViolation() {
|
||||
return true, pgerr.Field(68)
|
||||
}
|
||||
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
func GetError(errs *validator.ValidationErrors) string {
|
||||
size := len(*errs)
|
||||
msg := make([]string, size)
|
||||
for i, err := range *errs {
|
||||
key := err.Field()
|
||||
if len(key) == 0 {
|
||||
key = err.Tag()
|
||||
}
|
||||
msg[i] = fmt.Sprintf("%s %s", key, readableValidationError(err.Tag(), err.Param(), err.Value()))
|
||||
}
|
||||
|
||||
return strings.Join(msg, ". ")
|
||||
}
|
||||
|
||||
func readableValidationError(tag string, param string, value interface{}) string {
|
||||
switch tag {
|
||||
case "required":
|
||||
return tag
|
||||
case "lte":
|
||||
return fmt.Sprintf("greater than %s", param)
|
||||
case "gte":
|
||||
return fmt.Sprintf("less than %s", param)
|
||||
case "len":
|
||||
return fmt.Sprintf("not %s length", param)
|
||||
case "max":
|
||||
return fmt.Sprintf("greater than %s length", param)
|
||||
case "min":
|
||||
return fmt.Sprintf("less than %s length", param)
|
||||
case "serial":
|
||||
return fmt.Sprintf("'%v' invalid", value)
|
||||
case "vin":
|
||||
return fmt.Sprintf("'%v' invalid", value)
|
||||
case "url":
|
||||
return "invalid url"
|
||||
default:
|
||||
return fmt.Sprintf("%s %s", tag, param)
|
||||
}
|
||||
}
|
||||
9
pkg/validator/fielderror.go
Normal file
9
pkg/validator/fielderror.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package validator
|
||||
|
||||
type FieldError struct {
|
||||
ErrorMsg string
|
||||
}
|
||||
|
||||
func (fe *FieldError) Error() string {
|
||||
return fe.ErrorMsg
|
||||
}
|
||||
12
pkg/validator/fleet_name.go
Normal file
12
pkg/validator/fleet_name.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func validateFleetName(fl validator.FieldLevel) bool {
|
||||
ok, _ := regexp.Match(`^[a-zA-Z0-9-]+$`, []byte(fl.Field().String()))
|
||||
return ok
|
||||
}
|
||||
51
pkg/validator/fleet_name_test.go
Normal file
51
pkg/validator/fleet_name_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
)
|
||||
|
||||
type TestFleet struct {
|
||||
Name string `validate:"fleet"`
|
||||
Expected string
|
||||
}
|
||||
|
||||
var fleetValidatorValidTests = []TestFleet{
|
||||
{Name: "t"},
|
||||
{Name: "test"},
|
||||
{Name: "Test"},
|
||||
{Name: "TEST"},
|
||||
{Name: "test1"},
|
||||
{Name: "1test"},
|
||||
{Name: "test-test"},
|
||||
}
|
||||
|
||||
func TestValidateFleetName(t *testing.T) {
|
||||
var tests = []TestFleet{
|
||||
{
|
||||
Name: "",
|
||||
Expected: "Key: 'TestFleet.Name' Error:Field validation for 'Name' failed on the 'fleet' tag",
|
||||
},
|
||||
{
|
||||
Name: "$test",
|
||||
Expected: "Key: 'TestFleet.Name' Error:Field validation for 'Name' failed on the 'fleet' tag",
|
||||
},
|
||||
{
|
||||
Name: "test test",
|
||||
Expected: "Key: 'TestFleet.Name' Error:Field validation for 'Name' failed on the 'fleet' tag",
|
||||
},
|
||||
}
|
||||
|
||||
tests = append(tests, fleetValidatorValidTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.ValidateStruct(test)
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.Expected, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
27
pkg/validator/pg_order_by.go
Normal file
27
pkg/validator/pg_order_by.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// This allows just one order by
|
||||
func validateSqlOrderBy(fl validator.FieldLevel) bool {
|
||||
// ensure ORDER BY query section is valid
|
||||
// only letters and numbers
|
||||
// this will prevent the sql injection test from sending an error
|
||||
// because it sets the ORDER BY of a query to
|
||||
// "CASE WHEN (‘1’=’1’) THEN vin ELSE year END asc"
|
||||
strings := strings.Split(fl.Field().String(), " ")
|
||||
ex := regexp.MustCompile(`^[a-zA-Z0-9_]*$`)
|
||||
for _, val := range strings {
|
||||
ok := ex.MatchString(val)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
51
pkg/validator/pg_order_by_test.go
Normal file
51
pkg/validator/pg_order_by_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
)
|
||||
|
||||
type TestPageQueryOptions struct {
|
||||
Order string `json:"order" validate:"max=512,sqlorder"`
|
||||
Expected string
|
||||
}
|
||||
|
||||
func TestValidateSqlOrderBy(t *testing.T) {
|
||||
var tests = []TestPageQueryOptions{
|
||||
{
|
||||
Order: "",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
Order: "COLUMN",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
Order: "COLUMN DESC",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
Order: "COL_UMN DESC",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
Order: "CASE WHEN ('1'='1') THEN vin ELSE year END asc", // sql injection test
|
||||
Expected: "Key: 'TestPageQueryOptions.Order' Error:Field validation for 'Order' failed on the 'sqlorder' tag",
|
||||
},
|
||||
{ // This could be made to be valid
|
||||
Order: "col1 DESC, col2 DESC",
|
||||
Expected: "Key: 'TestPageQueryOptions.Order' Error:Field validation for 'Order' failed on the 'sqlorder' tag",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.ValidateStruct(test)
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Order, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Order, test.Expected, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
14
pkg/validator/update_manifest_version.go
Normal file
14
pkg/validator/update_manifest_version.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
const regexSUMSVersion = `^\d{4}\.(0[1-9]|1[0-2])\.\d{2}\.\d{2}(\.E{1})?$`
|
||||
|
||||
func validateSUMSVersion(fl validator.FieldLevel) bool {
|
||||
ok, _ := regexp.Match(regexSUMSVersion, []byte(fl.Field().String()))
|
||||
return ok
|
||||
}
|
||||
19
pkg/validator/user_consent.go
Normal file
19
pkg/validator/user_consent.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
const (
|
||||
GEO_LOCATION = "geolocation"
|
||||
CONNECTED_CAR_FEATURE = "connected_car_feature"
|
||||
NAV_LOCATION_DATA = "navlocation_data"
|
||||
ACCEPTED = "ACCEPTED"
|
||||
DECLINED = "DECLINED"
|
||||
)
|
||||
|
||||
func validateUserConsentName(fl validator.FieldLevel) bool {
|
||||
s := fl.Field().String()
|
||||
|
||||
return s == GEO_LOCATION || s == CONNECTED_CAR_FEATURE || s == NAV_LOCATION_DATA
|
||||
}
|
||||
91
pkg/validator/validator.go
Normal file
91
pkg/validator/validator.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
const TokenRule = "max=2048"
|
||||
const URLRule = "url,max=32768"
|
||||
const VersionRule = "max=255"
|
||||
|
||||
var instance *validator.Validate
|
||||
var once sync.Once
|
||||
|
||||
// GetValidator creates a singleton validator instance
|
||||
func GetValidator() *validator.Validate {
|
||||
once.Do(func() {
|
||||
instance = validator.New()
|
||||
instance.RegisterValidation("sqlorder", validateSqlOrderBy)
|
||||
instance.RegisterValidation("can_id", validateCANID)
|
||||
instance.RegisterValidation("fleet", validateFleetName)
|
||||
instance.RegisterValidation("serial", validateCertSerial)
|
||||
instance.RegisterValidation("vin", validateVIN)
|
||||
instance.RegisterValidation("vins", validateVINs)
|
||||
instance.RegisterValidation("vinsuffix", validateVINSuffix)
|
||||
instance.RegisterValidation("vincheck", validateVinCheckDigit)
|
||||
instance.RegisterValidation("email", validateEmail)
|
||||
instance.RegisterValidation("ecu", validateECU)
|
||||
instance.RegisterValidation("ISO8601date", IsISO8601Date)
|
||||
instance.RegisterValidation("yyyymmdddate", IsDateYYYYMMDD)
|
||||
instance.RegisterValidation("sums_version", validateSUMSVersion)
|
||||
instance.RegisterValidation("user_consent_name", validateUserConsentName)
|
||||
instance.RegisterValidation("iccid", validateICCID)
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
// ValidateStruct validates declared fields within a struct
|
||||
// variables must have "validate" tag declared within struct
|
||||
func ValidateStruct(s interface{}) error {
|
||||
return GetValidator().Struct(s)
|
||||
}
|
||||
|
||||
func ValidateField(field interface{}, tag string) error {
|
||||
return GetValidator().Var(field, tag)
|
||||
}
|
||||
|
||||
func ValidateIDField(v int64) error {
|
||||
if v == 0 {
|
||||
return &FieldError{
|
||||
ErrorMsg: "id required",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateURL(u string) bool {
|
||||
_, err := url.ParseRequestURI(u)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func ValidateNonRequired(s interface{}) error {
|
||||
err := GetValidator().Struct(s)
|
||||
return getNonRequired(err)
|
||||
}
|
||||
|
||||
func getNonRequired(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
valerrs, ok := err.(validator.ValidationErrors)
|
||||
if ok {
|
||||
nonrequired := make(validator.ValidationErrors, 0)
|
||||
for _, val := range valerrs {
|
||||
if !strings.Contains(val.Tag(), "required") {
|
||||
nonrequired = append(nonrequired, val)
|
||||
}
|
||||
}
|
||||
if len(nonrequired) > 0 {
|
||||
return nonrequired
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
201
pkg/validator/validator_test.go
Normal file
201
pkg/validator/validator_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
|
||||
v "github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
Name string
|
||||
Validate interface{}
|
||||
ExpectedError string
|
||||
}
|
||||
|
||||
func TestValidateStruct(t *testing.T) {
|
||||
tests := []TestCase{
|
||||
{
|
||||
Name: "Empty Car",
|
||||
Validate: common.Car{},
|
||||
ExpectedError: "VIN required. Year required. Model required. Trim required",
|
||||
},
|
||||
{
|
||||
Name: "Valid Car VIN",
|
||||
Validate: common.Car{
|
||||
VIN: "JTKJF5C77E3095776",
|
||||
},
|
||||
ExpectedError: "Year required. Model required. Trim required",
|
||||
},
|
||||
{
|
||||
Name: "Valid Car",
|
||||
Validate: common.Car{
|
||||
VIN: "JTKJF5C77E3095776",
|
||||
Year: 2022,
|
||||
Model: "Ocean",
|
||||
Trim: "Basic",
|
||||
},
|
||||
ExpectedError: "",
|
||||
},
|
||||
{
|
||||
Name: "Invalid Car VIN",
|
||||
Validate: common.Car{
|
||||
VIN: "JTKJF5C77E3095776X",
|
||||
},
|
||||
ExpectedError: "VIN 'JTKJF5C77E3095776X' invalid. Year required. Model required. Trim required",
|
||||
},
|
||||
}
|
||||
|
||||
validationTestRunner(t, tests, validator.ValidateStruct)
|
||||
}
|
||||
|
||||
func TestValidateSerialStruct(t *testing.T) {
|
||||
tests := []TestCase{
|
||||
{
|
||||
Name: "Empty Cert",
|
||||
Validate: common.CertificateRevokeRequest{},
|
||||
ExpectedError: "Serial required",
|
||||
},
|
||||
{
|
||||
Name: "Valid Cert Serial",
|
||||
Validate: common.CertificateRevokeRequest{
|
||||
Serial: "02-89-1f-ec-82-69-8a-ce-59-9c-ab-6a-ad-03-b3-c4-41-bd-0d-26",
|
||||
},
|
||||
ExpectedError: "",
|
||||
},
|
||||
{
|
||||
Name: "Valid Cert Serial 2",
|
||||
Validate: common.CertificateRevokeRequest{
|
||||
Serial: "02-89-1f-ec-82-69-8a-ce-59-9c-ab-6a-ad-03-b3-c4-41-bd-0d",
|
||||
},
|
||||
ExpectedError: "",
|
||||
},
|
||||
{
|
||||
Name: "Wrong Cert Serial",
|
||||
Validate: common.CertificateRevokeRequest{
|
||||
Serial: "XXXXXXXXXXX",
|
||||
},
|
||||
ExpectedError: "Serial 'XXXXXXXXXXX' invalid",
|
||||
},
|
||||
}
|
||||
|
||||
validationTestRunner(t, tests, validator.ValidateStruct)
|
||||
}
|
||||
|
||||
func TestValidateNonRequired(t *testing.T) {
|
||||
tests := []TestCase{
|
||||
{
|
||||
Name: "Invalid Car VIN",
|
||||
Validate: common.Car{
|
||||
VIN: "JTKJF5C77E3095776X",
|
||||
Model: "Ocean",
|
||||
Year: 2021,
|
||||
},
|
||||
ExpectedError: "VIN 'JTKJF5C77E3095776X' invalid",
|
||||
},
|
||||
{
|
||||
Name: "Empty Car",
|
||||
Validate: common.Car{},
|
||||
ExpectedError: "",
|
||||
},
|
||||
{
|
||||
Name: "Valid Car VIN",
|
||||
Validate: common.Car{
|
||||
VIN: "JTKJF5C77E3095776",
|
||||
},
|
||||
ExpectedError: "",
|
||||
},
|
||||
{
|
||||
Name: "Valid Car",
|
||||
Validate: common.Car{
|
||||
VIN: "JTKJF5C77E3095776",
|
||||
Year: 2022,
|
||||
Model: "Ocean",
|
||||
},
|
||||
ExpectedError: "",
|
||||
},
|
||||
}
|
||||
|
||||
validationTestRunner(t, tests, validator.ValidateNonRequired)
|
||||
}
|
||||
|
||||
func validationTestRunner(t *testing.T, tests []TestCase, validatorFunc func(interface{}) error) {
|
||||
for _, test := range tests {
|
||||
err := validatorFunc(test.Validate)
|
||||
if err != nil {
|
||||
valerrs, ok := err.(v.ValidationErrors)
|
||||
if ok {
|
||||
str := validator.GetError(&valerrs)
|
||||
if str != test.ExpectedError {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedError, str)
|
||||
}
|
||||
} else {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, "ValidationErrors", err)
|
||||
}
|
||||
} else if test.ExpectedError != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedError, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVINValidation(t *testing.T) {
|
||||
expected := "VIN '%s' invalid"
|
||||
test := common.Car{
|
||||
VIN: "1G1FP87S3GN100062",
|
||||
Model: "Ocean",
|
||||
Year: 2021,
|
||||
Trim: "Basic",
|
||||
}
|
||||
|
||||
err := validator.ValidateStruct(test)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "Good validation", "no errors", err)
|
||||
}
|
||||
|
||||
test.VIN = "1G1FP87S3GN100062XXXXX"
|
||||
|
||||
err = validator.ValidateStruct(test)
|
||||
if err != nil {
|
||||
_, msg := validator.GetValidationErrorMsg(err)
|
||||
if msg != fmt.Sprintf(expected, test.VIN) {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "Bad VIN", fmt.Sprintf(expected, test.VIN), msg)
|
||||
}
|
||||
}
|
||||
|
||||
test.VIN = "1G1FP87S3GN10006"
|
||||
|
||||
err = validator.ValidateStruct(test)
|
||||
if err != nil {
|
||||
_, msg := validator.GetValidationErrorMsg(err)
|
||||
if msg != fmt.Sprintf(expected, test.VIN) {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "Bad VIN", fmt.Sprintf(expected, test.VIN), msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePackageValidation(t *testing.T) {
|
||||
up := common.UpdateManifest{}
|
||||
|
||||
err := validator.ValidateStruct(up)
|
||||
if err != nil {
|
||||
_, msg := validator.GetValidationErrorMsg(err)
|
||||
expected := "Name required"
|
||||
if msg != expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "Empty UpdateManifest", expected, msg)
|
||||
}
|
||||
}
|
||||
|
||||
up.ReleaseNotes = "XXXXX"
|
||||
err = validator.ValidateStruct(up)
|
||||
if err != nil {
|
||||
_, msg := validator.GetValidationErrorMsg(err)
|
||||
expected := "Name required. ReleaseNotes invalid url"
|
||||
if msg != expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "Empty UpdateManifest", expected, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
72
pkg/validator/vin.go
Normal file
72
pkg/validator/vin.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/pkg/errors"
|
||||
"fiskerinc.com/modules/vindecoder"
|
||||
)
|
||||
|
||||
func validateVIN(fl validator.FieldLevel) bool {
|
||||
ok := ValidateVINSimple(fl.Field().String())
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func validateVINs(fl validator.FieldLevel) bool {
|
||||
vins := strings.Split(fl.Field().String(), ",")
|
||||
|
||||
for _, vin := range vins {
|
||||
ok := vindecoder.ValidateVINSimple(vin)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
ok = vindecoder.VerifyVinCheckDigit(vin)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func validateVINSuffix(fl validator.FieldLevel) bool {
|
||||
ok, err := ValidateVINSuffixSimple(fl.Field().String())
|
||||
|
||||
return ok && err == nil
|
||||
}
|
||||
|
||||
func ValidateVINSuffixSimple(vin string) (bool, error) {
|
||||
matched, err := regexp.Match(`^[a-hj-npr-zA-HJ-NPR-Z0-9]{7}$`, []byte(vin))
|
||||
if err != nil {
|
||||
return matched, errors.WithStack(err)
|
||||
}
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
func validateICCID(fl validator.FieldLevel) bool {
|
||||
ok, err := ValidateICCIDSimple(fl.Field().String())
|
||||
|
||||
return ok && err == nil
|
||||
}
|
||||
|
||||
func ValidateICCIDSimple(iccid string) (bool, error) {
|
||||
matched, err := regexp.Match(`^[0-9]{5,50}F{0,1}$`, []byte(iccid))
|
||||
if err != nil {
|
||||
return matched, errors.Wrapf(err, "")
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
func validateVinCheckDigit(fl validator.FieldLevel) bool {
|
||||
var vin = fl.Field().String()
|
||||
return vindecoder.VerifyVinCheckDigit(vin)
|
||||
}
|
||||
|
||||
func ValidateVINSimple(vin string) (valid bool) {
|
||||
return vindecoder.ValidateVINSimple(vin)
|
||||
}
|
||||
53
pkg/validator/vin_check_test.go
Normal file
53
pkg/validator/vin_check_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
)
|
||||
|
||||
func TestValidateVinCheckDigit(t *testing.T) {
|
||||
// these should pass vin regex validation
|
||||
//otherwise that error would be received here and the test run would fail because of expected tag name mismatch
|
||||
tests := []TestCar{
|
||||
{
|
||||
VIN: "WVWBN7AN1DE546002",
|
||||
//Expected: "Key: 'Error:Field' Error:Field validation for 'VIN' failed on the 'vincheck' tag",
|
||||
Expected: "Key: '' Error:Field validation for '' failed on the 'vincheck' tag",
|
||||
},
|
||||
{
|
||||
VIN: "WVWBC7AN1DE546002",
|
||||
//Expected: "Key: 'Error:Field' Error:Field validation for 'VIN' failed on the 'vincheck' tag",
|
||||
Expected: "Key: '' Error:Field validation for '' failed on the 'vincheck' tag",
|
||||
},
|
||||
}
|
||||
|
||||
tests = append(tests, vinValidatorValidTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.ValidateField(test.VIN, "vincheck")
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err.Error())
|
||||
}
|
||||
fmt.Println(test, err)
|
||||
}
|
||||
}
|
||||
|
||||
var vwGer2013Valid = "WVWBN7AN6DE546002"
|
||||
var vwGer2013Invalid1 = "WVWBN7AN1DE546002"
|
||||
var vwGer2013Invalid2 = "WVWBN7AN6DE54600"
|
||||
var miniGer2009Valid = "WMWMS335X9TY38985"
|
||||
var bugatFra1998Valid = "VF9SP3V31JM795073"
|
||||
var dodgeUsa1998Valid = "1B3ES42C4WD736523"
|
||||
|
||||
// TEMP
|
||||
var fskUsa2021Valid = "1F1BN7AN7MA000001"
|
||||
|
||||
// TEMP
|
||||
|
||||
//var unkJap2013Valid = "JS1GR7MA7D2101136"
|
||||
//var audiGer2012Valid = "WAUFFAFM3CA000000"
|
||||
166
pkg/validator/vin_test.go
Normal file
166
pkg/validator/vin_test.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/validator"
|
||||
)
|
||||
|
||||
type TestCar struct {
|
||||
VIN string `validate:"vin,len=17"`
|
||||
Expected string
|
||||
}
|
||||
|
||||
var vinValidatorValidTests = []TestCar{
|
||||
{
|
||||
VIN: "1G1FP87S3GN100062",
|
||||
},
|
||||
{
|
||||
VIN: "1HTSDN2NXPH482591",
|
||||
},
|
||||
{
|
||||
VIN: "2C4RDGCG0DR641898",
|
||||
},
|
||||
{
|
||||
VIN: "2FMZA5149YBC02439",
|
||||
},
|
||||
{
|
||||
VIN: "2G1FA1E38E9309317",
|
||||
},
|
||||
{
|
||||
VIN: "2HNYD18245H511789",
|
||||
},
|
||||
{
|
||||
VIN: "3C4PDCBG0ET127145",
|
||||
},
|
||||
{
|
||||
VIN: "4JGBB86E46A022490",
|
||||
},
|
||||
{
|
||||
VIN: "5UXCY6C01M9E72005",
|
||||
},
|
||||
{
|
||||
VIN: "JTDKN3DU7A0198862",
|
||||
},
|
||||
{
|
||||
VIN: "KL1TD56647B195973",
|
||||
},
|
||||
{
|
||||
VIN: "WBABS53402JU44262",
|
||||
},
|
||||
{
|
||||
VIN: "YV1CZ852251176667",
|
||||
},
|
||||
}
|
||||
|
||||
func TestValidateVIN(t *testing.T) {
|
||||
var tests = []TestCar{
|
||||
{
|
||||
VIN: "XXXXXXXXXXXX",
|
||||
Expected: "Key: 'TestCar.VIN' Error:Field validation for 'VIN' failed on the 'vin' tag",
|
||||
},
|
||||
{
|
||||
VIN: "XXXXXXXXXXXXXXXXXXXXXX",
|
||||
Expected: "Key: 'TestCar.VIN' Error:Field validation for 'VIN' failed on the 'vin' tag",
|
||||
},
|
||||
}
|
||||
|
||||
tests = append(tests, vinValidatorValidTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.ValidateStruct(test)
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.VIN, test.Expected, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateVINs(t *testing.T) {
|
||||
tests := []struct {
|
||||
VINs string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
VINs: "1G1FP87S3GN100062,1HTSDN2NXPH482591,2C4RDGCG0DR641898,2FMZA5149YBC02439",
|
||||
},
|
||||
{
|
||||
VINs: "1G1FP87S3GN100062,1HTSDN2NXPH482591,2C4RDGCG0DR641898,2FMZA5149YBC02439,XXXXXXXXXXXX",
|
||||
Expected: "Key: '' Error:Field validation for '' failed on the 'vins' tag",
|
||||
},
|
||||
{
|
||||
VINs: "XXXXXXXXXXXX,YYYYYYYYYYYY,ZZZZZZZZZZZZ",
|
||||
Expected: "Key: '' Error:Field validation for '' failed on the 'vins' tag",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.GetValidator().Var(test.VINs, "vins")
|
||||
if err == nil && test.Expected != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.VINs, test.Expected, err)
|
||||
} else if err != nil && err.Error() != test.Expected {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.VINs, test.Expected, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateVINSuffix(t *testing.T) {
|
||||
tests := []struct {
|
||||
vin string
|
||||
expErr string
|
||||
}{
|
||||
{vin: "N100062"},
|
||||
{vin: "H482591"},
|
||||
{vin: "R641898"},
|
||||
{vin: "BC02439"},
|
||||
{vin: "9309317"},
|
||||
{vin: "H511789"},
|
||||
{vin: "T127145"},
|
||||
{vin: "A022490"},
|
||||
{vin: "9E72005"},
|
||||
{vin: "0198862"},
|
||||
{vin: "B195973"},
|
||||
{vin: "JU44262"},
|
||||
{vin: "1176667"},
|
||||
{vin: "XXXXXX", expErr: "Key: '' Error:Field validation for '' failed on the 'vinsuffix' tag"},
|
||||
{vin: "XXXXXXXX", expErr: "Key: '' Error:Field validation for '' failed on the 'vinsuffix' tag"},
|
||||
{vin: "XXXXXXO", expErr: "Key: '' Error:Field validation for '' failed on the 'vinsuffix' tag"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := validator.GetValidator().Var(test.vin, "vinsuffix")
|
||||
if err == nil && test.expErr != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.vin, test.expErr, err)
|
||||
} else if err != nil && err.Error() != test.expErr {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.vin, test.expErr, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateICCIDSimple(t *testing.T) {
|
||||
tests := []struct {
|
||||
iccid string
|
||||
match bool
|
||||
expErr string
|
||||
}{
|
||||
{iccid: "8901882000784174124F", match: true},
|
||||
{iccid: "8901882000784174124", match: true},
|
||||
{iccid: "8901882000784174124FF"},
|
||||
{iccid: "SSSSSSSSS"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
matched, err := validator.ValidateICCIDSimple(test.iccid)
|
||||
if matched != test.match {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.iccid, test.match, matched)
|
||||
}
|
||||
if err == nil && test.expErr != "" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.iccid, test.expErr, err)
|
||||
} else if err != nil && err.Error() != test.expErr {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.iccid, test.expErr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user