Initial commit, valiwork as framework is ready to use.
This commit is contained in:
commit
169b9499fe
83
.drone.yml
Normal file
83
.drone.yml
Normal file
@ -0,0 +1,83 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build
|
||||
|
||||
steps:
|
||||
- name: notify-start
|
||||
image: pztrn/discordrone
|
||||
settings:
|
||||
webhook_id:
|
||||
from_secret: discord_webhook_id
|
||||
webhook_token:
|
||||
from_secret: discord_webhook_secret
|
||||
message: 'Starting testing, benchmarking and linting for **{{repo.name}}#{{build.number}}@{{commit.sha}}** @ {{datetime build.started "02-Jan-2006 15:04:05 MST" "Asia/Yekaterinburg"}} (See {{build.link}} for logs).'
|
||||
|
||||
- name: lint
|
||||
image: golangci/golangci-lint:latest
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- golangci-lint run
|
||||
depends_on:
|
||||
- notify-start
|
||||
|
||||
- name: test
|
||||
image: golang:1.13.4-alpine
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- go test -test.v ./...
|
||||
depends_on:
|
||||
- notify-start
|
||||
|
||||
- name: test-race
|
||||
image: golang:1.13.4-alpine
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- go test -race -test.v ./...
|
||||
depends_on:
|
||||
- notify-start
|
||||
|
||||
- name: benchmark
|
||||
image: golang:1.13.4-alpine
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- go test -benchmem -run=^$ go.dev.pztrn.name/valiwork -bench .
|
||||
depends_on:
|
||||
- notify-start
|
||||
|
||||
- name: benchmark-race
|
||||
image: golang:1.13.4-alpine
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
commands:
|
||||
- go test -benchmem -run=^$ go.dev.pztrn.name/valiwork -race -bench .
|
||||
depends_on:
|
||||
- notify-start
|
||||
|
||||
- name: notify-end
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
image: pztrn/discordrone
|
||||
settings:
|
||||
webhook_id:
|
||||
from_secret: discord_webhook_id
|
||||
webhook_token:
|
||||
from_secret: discord_webhook_secret
|
||||
message: "
|
||||
{{#success build.status}}
|
||||
**{{repo.name}}#{{build.number}}@{{commit.sha}}** tested, benchmarked and linted successfully.
|
||||
{{ else }}
|
||||
**{{repo.name}}#{{build.number}}@{{commit.sha}}** failed. See {{build.link}}.
|
||||
{{/success}}"
|
||||
depends_on:
|
||||
- benchmark
|
||||
- benchmark-race
|
||||
- lint
|
||||
- test
|
||||
- test-race
|
17
.golangci.yml
Normal file
17
.golangci.yml
Normal file
@ -0,0 +1,17 @@
|
||||
run:
|
||||
deadline: 5m
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
# Because globals might exist, but according to our codestyle they
|
||||
# should be lowercased and considered as unexported.
|
||||
- gochecknoglobals
|
||||
# While it might be useful it'll create more problems that will solve.
|
||||
- gocritic
|
||||
# This linter goes crazy for nothing (almost).
|
||||
- funlen
|
||||
linters-settings:
|
||||
lll:
|
||||
line-length: 420
|
||||
gocyclo:
|
||||
min-complexity: 40
|
27
README.md
Normal file
27
README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# ValiWork - validation framework
|
||||
|
||||
ValiWork is a validation framework that provides sane API and ability to write own validators that returns arbitrary things. It is goroutine-safe and fast.
|
||||
|
||||
## Default validators
|
||||
|
||||
There are no necessity to enable default validators at all. But if you want to - call:
|
||||
|
||||
```go
|
||||
valiwork.InitializeDefaultValidators()
|
||||
```
|
||||
|
||||
Default validators will return ``error``.
|
||||
|
||||
## Validators registering and namespacing
|
||||
|
||||
Default validators using "T_N" scheme, where ``T`` is data type (string, int, int64, etc.) and ``N`` is a validator name (which can be a generic string). Please, use same naming scheme. Example good validators names:
|
||||
|
||||
* ``string_check_for_very_rare_symbol_that_is_not_allowed``
|
||||
* ``int64_check_if_in_bad_range``
|
||||
* ``interface_check_if_able_to_be_TheVeryGoodStruct``
|
||||
|
||||
Key idea is to help you debugging this thing (see [debug section](#Debug) below).
|
||||
|
||||
## Debug
|
||||
|
||||
Define ``VALIWORK_DEBUG`` environment variable and set it to ``true`` to get debug output. Default ``log`` module will be used for that.
|
25
debug.go
Normal file
25
debug.go
Normal file
@ -0,0 +1,25 @@
|
||||
package valiwork
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
DEBUG bool
|
||||
)
|
||||
|
||||
// Initializes debug output.
|
||||
// nolint
|
||||
func init() {
|
||||
debug, found := os.LookupEnv("VALIWORK_DEBUG")
|
||||
if found {
|
||||
debugBool, err := strconv.ParseBool(debug)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
DEBUG = debugBool
|
||||
}
|
||||
}
|
17
errors.go
Normal file
17
errors.go
Normal file
@ -0,0 +1,17 @@
|
||||
package valiwork
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrValidatorAlreadyRegistered appears when validator's name
|
||||
// passed to RegisterValidator function already used for other
|
||||
// validator function.
|
||||
ErrValidatorAlreadyRegistered = errors.New("validator with such name already registered")
|
||||
|
||||
// ErrValidatorNotRegistered appears when trying to unregister
|
||||
// not registered validator function.
|
||||
ErrValidatorNotRegistered = errors.New("validator with such name wasn't registered")
|
||||
)
|
43
example/main.go
Normal file
43
example/main.go
Normal file
@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
// stdlib
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
// other
|
||||
"go.dev.pztrn.name/valiwork"
|
||||
)
|
||||
|
||||
const (
|
||||
stringValidatorName = "string_validate_things"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("Starting validation example...")
|
||||
log.Println("WARN: to see additional valiwork output define 'VALIWORK_DEBUG' environment variable and set it to 'true'!")
|
||||
|
||||
//stringToValidate := " I am pretty b@d $tring"
|
||||
|
||||
valiwork.RegisterValidator(stringValidatorName, stringValidator)
|
||||
}
|
||||
|
||||
func stringValidator(thing interface{}, optional ...interface{}) []interface{} {
|
||||
var errs []interface{}
|
||||
|
||||
stringToValidate, ok := thing.(string)
|
||||
if !ok {
|
||||
errs = append(errs, errors.New("passed value is not a string"))
|
||||
return errs
|
||||
}
|
||||
|
||||
// Are string begins with spaces?
|
||||
if strings.HasPrefix(stringToValidate, " ") {
|
||||
errs = append(errs, errors.New("string begins with space"))
|
||||
}
|
||||
|
||||
// Does string contains any special characters?
|
||||
|
||||
return errs
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module go.dev.pztrn.name/valiwork
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/stretchr/testify v1.4.0
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
97
validations.go
Normal file
97
validations.go
Normal file
@ -0,0 +1,97 @@
|
||||
package valiwork
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/valiwork/validators"
|
||||
)
|
||||
|
||||
var (
|
||||
registeredValidators map[string]validators.ValidatorFunc
|
||||
rvMutex sync.RWMutex
|
||||
)
|
||||
|
||||
// nolint
|
||||
func init() {
|
||||
initializeValidatorsStorage()
|
||||
}
|
||||
|
||||
func initializeValidatorsStorage() {
|
||||
registeredValidators = make(map[string]validators.ValidatorFunc)
|
||||
}
|
||||
|
||||
// RegisterValidator registers validation function for later calling.
|
||||
func RegisterValidator(validatorName string, validator validators.ValidatorFunc) error {
|
||||
if DEBUG {
|
||||
log.Println("Trying to register validator: '" + validatorName + "'...")
|
||||
}
|
||||
|
||||
//rvMutex.RLock()
|
||||
_, found := registeredValidators[validatorName]
|
||||
//rvMutex.RUnlock()
|
||||
|
||||
if found {
|
||||
if DEBUG {
|
||||
log.Println("Validator already registered!")
|
||||
}
|
||||
|
||||
return ErrValidatorAlreadyRegistered
|
||||
}
|
||||
|
||||
//rvMutex.Lock()
|
||||
registeredValidators[validatorName] = validator
|
||||
//rvMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnregisterValidator removes registered validator from list of known
|
||||
// validators.
|
||||
func UnregisterValidator(validatorName string) error {
|
||||
if DEBUG {
|
||||
log.Println("Trying to unregister validator '" + validatorName + "'...")
|
||||
}
|
||||
|
||||
//rvMutex.RLock()
|
||||
_, found := registeredValidators[validatorName]
|
||||
//rvMutex.RUnlock()
|
||||
|
||||
if !found {
|
||||
if DEBUG {
|
||||
log.Println("Validator wasn't registered!")
|
||||
}
|
||||
|
||||
return ErrValidatorNotRegistered
|
||||
}
|
||||
|
||||
//rvMutex.Lock()
|
||||
delete(registeredValidators, validatorName)
|
||||
//rvMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate launches validation function and returns it's result to
|
||||
// caller.
|
||||
func Validate(validatorName string, thing interface{}, optional ...interface{}) []interface{} {
|
||||
var errs []interface{}
|
||||
|
||||
//rvMutex.RLock()
|
||||
validator, found := registeredValidators[validatorName]
|
||||
//rvMutex.RUnlock()
|
||||
|
||||
if !found {
|
||||
errs = append(errs, ErrValidatorNotRegistered)
|
||||
return errs
|
||||
}
|
||||
|
||||
errs1 := validator(thing, optional...)
|
||||
if len(errs1) > 0 {
|
||||
errs = append(errs, errs1...)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
266
validations_test.go
Normal file
266
validations_test.go
Normal file
@ -0,0 +1,266 @@
|
||||
package valiwork
|
||||
|
||||
import (
|
||||
// stdlib
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
// local
|
||||
"go.dev.pztrn.name/valiwork/validators"
|
||||
|
||||
// other
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRegisterValidator(t *testing.T) {
|
||||
initializeValidatorsStorage()
|
||||
|
||||
testCases := []struct {
|
||||
ValidatorName string
|
||||
ValidatorFunc validators.ValidatorFunc
|
||||
ShouldFail bool
|
||||
}{
|
||||
{
|
||||
ValidatorName: "string_test_validator",
|
||||
ValidatorFunc: func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
return nil
|
||||
},
|
||||
ShouldFail: false,
|
||||
},
|
||||
// This case is about registering same validator function again.
|
||||
{
|
||||
ValidatorName: "string_test_validator",
|
||||
ValidatorFunc: func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
return nil
|
||||
},
|
||||
ShouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := RegisterValidator(testCase.ValidatorName, testCase.ValidatorFunc)
|
||||
if !testCase.ShouldFail {
|
||||
require.Nil(t, err)
|
||||
} else {
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRegisterValidator(b *testing.B) {
|
||||
initializeValidatorsStorage()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = RegisterValidator("string_test_validator_"+strconv.Itoa(i),
|
||||
func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRegisterValidatorAsync(b *testing.B) {
|
||||
initializeValidatorsStorage()
|
||||
|
||||
var w sync.WaitGroup
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
w.Add(1)
|
||||
|
||||
go func() {
|
||||
_ = RegisterValidator("string_test_validator_"+strconv.Itoa(i),
|
||||
func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
w.Done()
|
||||
}()
|
||||
|
||||
w.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
initializeValidatorsStorage()
|
||||
|
||||
testString := " I am test string"
|
||||
|
||||
RegisterValidator("string_test1", func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
var errs []interface{}
|
||||
|
||||
stringToValidate, ok := thing.(string)
|
||||
if !ok {
|
||||
errs = append(errs, errors.New("not a string"))
|
||||
return errs
|
||||
}
|
||||
|
||||
if strings.HasPrefix(stringToValidate, " ") {
|
||||
errs = append(errs, errors.New("string starts with whitespace, invalid!"))
|
||||
}
|
||||
|
||||
return errs
|
||||
})
|
||||
|
||||
errs := Validate("string_test1", testString, nil)
|
||||
require.NotNil(t, errs)
|
||||
require.Len(t, errs, 1)
|
||||
}
|
||||
|
||||
func BenchmarkValidate(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
initializeValidatorsStorage()
|
||||
|
||||
testString := " I am test $tring"
|
||||
|
||||
RegisterValidator("string_test1", func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
var errs []interface{}
|
||||
|
||||
stringToValidate, ok := thing.(string)
|
||||
if !ok {
|
||||
errs = append(errs, errors.New("not a string"))
|
||||
return errs
|
||||
}
|
||||
|
||||
if strings.HasPrefix(stringToValidate, " ") {
|
||||
errs = append(errs, errors.New("string starts with whitespace, invalid!"))
|
||||
}
|
||||
|
||||
if strings.Contains(stringToValidate, "$") {
|
||||
errs = append(errs, errors.New("string starts with whitespace, invalid!"))
|
||||
}
|
||||
|
||||
return errs
|
||||
})
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Validate("string_test1", testString)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkValidateAsync(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
initializeValidatorsStorage()
|
||||
|
||||
testString := " I am test $tring"
|
||||
|
||||
RegisterValidator("string_test1", func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
var errs []interface{}
|
||||
|
||||
stringToValidate, ok := thing.(string)
|
||||
if !ok {
|
||||
errs = append(errs, errors.New("not a string"))
|
||||
return errs
|
||||
}
|
||||
|
||||
if strings.HasPrefix(stringToValidate, " ") {
|
||||
errs = append(errs, errors.New("string starts with whitespace, invalid!"))
|
||||
}
|
||||
|
||||
if strings.Contains(stringToValidate, "$") {
|
||||
errs = append(errs, errors.New("string starts with whitespace, invalid!"))
|
||||
}
|
||||
|
||||
return errs
|
||||
})
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
var w sync.WaitGroup
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
w.Add(1)
|
||||
|
||||
go func() {
|
||||
_ = Validate("string_test1", testString)
|
||||
|
||||
w.Done()
|
||||
}()
|
||||
|
||||
w.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnregisterValidator(t *testing.T) {
|
||||
initializeValidatorsStorage()
|
||||
|
||||
testCases := []struct {
|
||||
ValidatorName string
|
||||
ValidatorFunc validators.ValidatorFunc
|
||||
}{
|
||||
{
|
||||
ValidatorName: "string_test_validator",
|
||||
ValidatorFunc: func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := RegisterValidator(testCase.ValidatorName, testCase.ValidatorFunc)
|
||||
require.Nil(t, err)
|
||||
|
||||
err1 := UnregisterValidator(testCase.ValidatorName)
|
||||
require.Nil(t, err1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnregisterValidatorNotRegisteredValidator(t *testing.T) {
|
||||
initializeValidatorsStorage()
|
||||
|
||||
err := UnregisterValidator("this is definetely not registered thing")
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func BenchmarkUnregisterValidator(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
initializeValidatorsStorage()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = RegisterValidator("string_test_validator_"+strconv.Itoa(i),
|
||||
func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = UnregisterValidator("string_test_validator_" + strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnregisterValidatorAsync(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
initializeValidatorsStorage()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = RegisterValidator("string_test_validator_"+strconv.Itoa(i),
|
||||
func(thing interface{}, optional ...interface{}) []interface{} {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var w sync.WaitGroup
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
w.Add(1)
|
||||
|
||||
go func() {
|
||||
_ = UnregisterValidator("string_test_validator_" + strconv.Itoa(i))
|
||||
w.Done()
|
||||
}()
|
||||
|
||||
w.Wait()
|
||||
}
|
||||
}
|
4
validators/validator_signature.go
Normal file
4
validators/validator_signature.go
Normal file
@ -0,0 +1,4 @@
|
||||
package validators
|
||||
|
||||
// ValidatorFunc represents signature for data validation function.
|
||||
type ValidatorFunc func(thing interface{}, optional ...interface{}) []interface{}
|
Loading…
Reference in New Issue
Block a user