Implement value validators on fields
This commit is contained in:
@@ -96,6 +96,9 @@ func (vldtr *validator) FileRefChecker(fn ReferenceChecker) {
|
||||
func (vldtr validator) Run(ctx context.Context, s store.Storer, m *types.Module, r *types.Record) (out *types.RecordValueErrorSet) {
|
||||
var (
|
||||
f *types.ModuleField
|
||||
|
||||
valParser = parser()
|
||||
valDict = r.Values.Dict(m.Fields)
|
||||
)
|
||||
|
||||
out = &types.RecordValueErrorSet{}
|
||||
@@ -132,7 +135,7 @@ fields:
|
||||
}
|
||||
|
||||
for _, v := range r.Values {
|
||||
if !v.IsUpdated() || v.IsUpdated() {
|
||||
if !v.IsUpdated() || v.IsDeleted() {
|
||||
// We'll validate only updated (and non-deleted) values
|
||||
continue
|
||||
}
|
||||
@@ -141,33 +144,67 @@ fields:
|
||||
continue
|
||||
}
|
||||
|
||||
if v.Value == "" {
|
||||
// Nothing to do with empty value
|
||||
return nil
|
||||
if !(f.Expressions.DisableDefaultValidators && len(f.Expressions.Validators) > 0) {
|
||||
if v.Value == "" {
|
||||
// Nothing to do with empty value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Per field type validators
|
||||
switch strings.ToLower(f.Kind) {
|
||||
case "bool":
|
||||
out.Push(vldtr.vBool(v, f, r, m)...)
|
||||
case "datetime":
|
||||
out.Push(vldtr.vDatetime(v, f, r, m)...)
|
||||
case "email":
|
||||
out.Push(vldtr.vEmail(v, f, r, m)...)
|
||||
case "file":
|
||||
out.Push(vldtr.vFile(ctx, s, v, f, r, m)...)
|
||||
case "number":
|
||||
out.Push(vldtr.vNumber(v, f, r, m)...)
|
||||
case "record":
|
||||
out.Push(vldtr.vRecord(ctx, s, v, f, r, m)...)
|
||||
case "select":
|
||||
out.Push(vldtr.vSelect(v, f, r, m)...)
|
||||
//case "string":
|
||||
// out.Push(vldtr.vString(v, f, r, m)...)
|
||||
case "url":
|
||||
out.Push(vldtr.vUrl(v, f, r, m)...)
|
||||
case "user":
|
||||
out.Push(vldtr.vUser(ctx, s, v, f, r, m)...)
|
||||
}
|
||||
}
|
||||
|
||||
// Per field type validators
|
||||
switch strings.ToLower(f.Kind) {
|
||||
case "bool":
|
||||
out.Push(vldtr.vBool(v, f, r, m)...)
|
||||
case "datetime":
|
||||
out.Push(vldtr.vDatetime(v, f, r, m)...)
|
||||
case "email":
|
||||
out.Push(vldtr.vEmail(v, f, r, m)...)
|
||||
case "file":
|
||||
out.Push(vldtr.vFile(ctx, s, v, f, r, m)...)
|
||||
case "number":
|
||||
out.Push(vldtr.vNumber(v, f, r, m)...)
|
||||
case "record":
|
||||
out.Push(vldtr.vRecord(ctx, s, v, f, r, m)...)
|
||||
case "select":
|
||||
out.Push(vldtr.vSelect(v, f, r, m)...)
|
||||
//case "string":
|
||||
// out.Push(vldtr.vString(v, f, r, m)...)
|
||||
case "url":
|
||||
out.Push(vldtr.vUrl(v, f, r, m)...)
|
||||
case "user":
|
||||
out.Push(vldtr.vUser(ctx, s, v, f, r, m)...)
|
||||
if len(f.Expressions.Validators) > 0 {
|
||||
for _, cv := range f.Expressions.Validators {
|
||||
eval, err := valParser.NewEvaluable(cv.Test)
|
||||
if err != nil {
|
||||
out.Push(makeInternalErr(f, err))
|
||||
break
|
||||
}
|
||||
|
||||
invalid, err := eval.EvalBool(ctx, map[string]interface{}{
|
||||
"value": v.Value,
|
||||
"oldValue": v.OldValue,
|
||||
"values": valDict,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
out.Push(makeInternalErr(f, err))
|
||||
break
|
||||
}
|
||||
|
||||
if invalid {
|
||||
out.Push(types.RecordValueError{
|
||||
Kind: "error",
|
||||
Message: cv.Error,
|
||||
Meta: map[string]interface{}{"field": f.Name}},
|
||||
)
|
||||
|
||||
// break at first failed test
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -218,3 +220,25 @@ func Test_validator_vSelect(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validator_customExpr(t *testing.T) {
|
||||
var (
|
||||
vldtr = validator{}
|
||||
m = &types.Module{}
|
||||
f = &types.ModuleField{Name: "num", Kind: "Number"}
|
||||
r = &types.Record{}
|
||||
)
|
||||
|
||||
f.Expressions.Validators = []types.ModuleFieldValidator{
|
||||
{Test: "value > 5", Error: "value is lower than 5"},
|
||||
}
|
||||
m.Fields = append(m.Fields, f)
|
||||
r.Values = r.Values.Replace("num", "1")
|
||||
|
||||
rve := vldtr.Run(context.Background(), nil, m, r)
|
||||
require.False(t, rve.IsValid())
|
||||
|
||||
r.Values = r.Values.Replace("num", "10")
|
||||
rve = vldtr.Run(context.Background(), nil, m, r)
|
||||
require.True(t, rve.IsValid())
|
||||
}
|
||||
|
||||
@@ -45,14 +45,18 @@ type (
|
||||
ModuleFieldExpr struct {
|
||||
Value string `json:"value,omitempty"`
|
||||
Sanitizers []string `json:"sanitizers,omitempty"`
|
||||
//Validators []struct {
|
||||
// Test string `json:"test,omitempty"`
|
||||
// Error string `json:"error,omitempty"`
|
||||
//} `json:"validators,omitempty"`
|
||||
|
||||
Validators []ModuleFieldValidator `json:"validators,omitempty"`
|
||||
DisableDefaultValidators bool `json:"disableDefaultValidators,omitempty"`
|
||||
|
||||
Formatters []string `json:"formatters,omitempty"`
|
||||
DisableDefaultFormatters bool `json:"disableDefaultFormatters,omitempty"`
|
||||
}
|
||||
|
||||
ModuleFieldValidator struct {
|
||||
Test string `json:"test,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -96,9 +96,10 @@ func (set RecordValueSet) Replace(name string, values ...string) (vv RecordValue
|
||||
|
||||
for p, v := range values {
|
||||
vv = append(vv, &RecordValue{
|
||||
Name: name,
|
||||
Value: v,
|
||||
Place: uint(p),
|
||||
Name: name,
|
||||
Value: v,
|
||||
Place: uint(p),
|
||||
Updated: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user