Initial commit, valiwork as framework is ready to use.

This commit is contained in:
Stanislav Nikitin 2019-12-03 21:21:57 +05:00
commit 169b9499fe
No known key found for this signature in database
GPG Key ID: 106900B32F8192EE
11 changed files with 596 additions and 0 deletions

83
.drone.yml Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()
}
}

View File

@ -0,0 +1,4 @@
package validators
// ValidatorFunc represents signature for data validation function.
type ValidatorFunc func(thing interface{}, optional ...interface{}) []interface{}