feat: update string type checker | test: string, int, float, any typecheckers
feat: string type checker pre-process min,max/fixed length(s) before returning the CheckerFunc feat: add typecheckers for 'any', 'float' and 'int' (managing number overflows)
This commit is contained in:
parent
bafaad97e6
commit
0780e8dd33
|
@ -0,0 +1,78 @@
|
||||||
|
package builtin_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.xdrm.io/go/aicra/typecheck/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAny_New(t *testing.T) {
|
||||||
|
inst := interface{}(builtin.NewAny())
|
||||||
|
|
||||||
|
switch cast := inst.(type) {
|
||||||
|
case *builtin.Any:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Errorf("expect %T ; got %T", &builtin.Any{}, cast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAny_AvailableTypes(t *testing.T) {
|
||||||
|
|
||||||
|
inst := builtin.NewAny()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Type string
|
||||||
|
Handled bool
|
||||||
|
}{
|
||||||
|
{"any", true},
|
||||||
|
{" any", false},
|
||||||
|
{"any ", false},
|
||||||
|
{" any ", false},
|
||||||
|
{"Any", false},
|
||||||
|
{"ANY", false},
|
||||||
|
{"anything-else", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
checker := inst.Checker(test.Type)
|
||||||
|
|
||||||
|
if checker == nil {
|
||||||
|
if test.Handled {
|
||||||
|
t.Errorf("expect %q to be handled", test.Type)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !test.Handled {
|
||||||
|
t.Errorf("expect %q NOT to be handled", test.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAny_AlwaysTrue(t *testing.T) {
|
||||||
|
const typeName = "any"
|
||||||
|
|
||||||
|
checker := builtin.NewAny().Checker(typeName)
|
||||||
|
if checker == nil {
|
||||||
|
t.Errorf("expect %q to be handled", typeName)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
values := []interface{}{
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
nil,
|
||||||
|
"string",
|
||||||
|
[]byte("bytes"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, value := range values {
|
||||||
|
if !checker(value) {
|
||||||
|
t.Errorf("%d: expect value to be valid", i)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package builtin
|
package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"encoding/json"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/typecheck"
|
"git.xdrm.io/go/aicra/typecheck"
|
||||||
)
|
)
|
||||||
|
@ -41,7 +41,8 @@ func readFloat(value interface{}) (float64, bool) {
|
||||||
|
|
||||||
// serialized string -> try to convert to float
|
// serialized string -> try to convert to float
|
||||||
case string:
|
case string:
|
||||||
floatVal, err := strconv.ParseFloat(cast, 64)
|
num := json.Number(cast)
|
||||||
|
floatVal, err := num.Float64()
|
||||||
return floatVal, err == nil
|
return floatVal, err == nil
|
||||||
|
|
||||||
// unknown type
|
// unknown type
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package builtin
|
package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/typecheck"
|
"git.xdrm.io/go/aicra/typecheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,12 +22,37 @@ func (Int) Checker(typeName string) typecheck.CheckerFunc {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return func(value interface{}) bool {
|
return func(value interface{}) bool {
|
||||||
cast, isFloat := readFloat(value)
|
_, isInt := readInt(value)
|
||||||
|
|
||||||
|
return isInt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt tries to read a serialized int and returns whether it succeeded.
|
||||||
|
func readInt(value interface{}) (int, bool) {
|
||||||
|
switch cast := value.(type) {
|
||||||
|
|
||||||
|
case int:
|
||||||
|
return cast, true
|
||||||
|
|
||||||
|
case uint:
|
||||||
|
overflows := cast > math.MaxInt64
|
||||||
|
return int(cast), !overflows
|
||||||
|
|
||||||
|
case float64:
|
||||||
|
intVal := int(cast)
|
||||||
|
overflows := cast < float64(math.MinInt64) || cast > float64(math.MaxInt64)
|
||||||
|
return intVal, cast == float64(intVal) && !overflows
|
||||||
|
|
||||||
|
// serialized string -> try to convert to float
|
||||||
|
case string:
|
||||||
|
num := json.Number(cast)
|
||||||
|
intVal, err := num.Int64()
|
||||||
|
return int(intVal), err == nil
|
||||||
|
|
||||||
|
// unknown type
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
|
||||||
if !isFloat {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return cast == float64(int(cast))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package builtin_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.xdrm.io/go/aicra/typecheck/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInt_New(t *testing.T) {
|
||||||
|
inst := interface{}(builtin.NewInt())
|
||||||
|
|
||||||
|
switch cast := inst.(type) {
|
||||||
|
case *builtin.Int:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Errorf("expect %T ; got %T", &builtin.Int{}, cast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt_AvailableTypes(t *testing.T) {
|
||||||
|
|
||||||
|
inst := builtin.NewInt()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Type string
|
||||||
|
Handled bool
|
||||||
|
}{
|
||||||
|
{"int", true},
|
||||||
|
{"Int", false},
|
||||||
|
{"INT", false},
|
||||||
|
{" int", false},
|
||||||
|
{"int ", false},
|
||||||
|
{" int ", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
checker := inst.Checker(test.Type)
|
||||||
|
|
||||||
|
if checker == nil {
|
||||||
|
if test.Handled {
|
||||||
|
t.Errorf("expect %q to be handled", test.Type)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !test.Handled {
|
||||||
|
t.Errorf("expect %q NOT to be handled", test.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt_Values(t *testing.T) {
|
||||||
|
const typeName = "int"
|
||||||
|
|
||||||
|
checker := builtin.NewInt().Checker(typeName)
|
||||||
|
if checker == nil {
|
||||||
|
t.Errorf("expect %q to be handled", typeName)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Value interface{}
|
||||||
|
Valid bool
|
||||||
|
}{
|
||||||
|
{-math.MaxInt64, true},
|
||||||
|
{-1, true},
|
||||||
|
{0, true},
|
||||||
|
{1, true},
|
||||||
|
{math.MaxInt64, true},
|
||||||
|
|
||||||
|
// overflows from type conversion
|
||||||
|
{uint(math.MaxInt64), true},
|
||||||
|
{uint(math.MaxInt64 + 1), false},
|
||||||
|
|
||||||
|
{float64(math.MinInt64), true},
|
||||||
|
// we cannot just substract 1 because of how precision works
|
||||||
|
{float64(math.MinInt64 - 1024 - 1), false},
|
||||||
|
|
||||||
|
// WARNING : this is due to how floats are compared
|
||||||
|
{float64(math.MaxInt64), false},
|
||||||
|
// we cannot just add 1 because of how precision works
|
||||||
|
{float64(math.MaxInt64 + 1024 + 2), false},
|
||||||
|
|
||||||
|
{"string", false},
|
||||||
|
{[]byte("bytes"), false},
|
||||||
|
{-0.1, false},
|
||||||
|
{0.1, false},
|
||||||
|
{nil, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
if checker(test.Value) {
|
||||||
|
if !test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be invalid", i)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be valid", i)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,45 +29,49 @@ func (s String) Checker(typeName string) typecheck.CheckerFunc {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mustFail bool
|
||||||
|
min, max int
|
||||||
|
)
|
||||||
|
|
||||||
|
// extract fixed length
|
||||||
|
if fixedLengthMatches != nil {
|
||||||
|
exLen, ok := s.getFixedLength(fixedLengthMatches)
|
||||||
|
if !ok {
|
||||||
|
mustFail = true
|
||||||
|
}
|
||||||
|
min = exLen
|
||||||
|
max = exLen
|
||||||
|
|
||||||
|
// extract variable length
|
||||||
|
} else if variableLengthMatches != nil {
|
||||||
|
exMin, exMax, ok := s.getVariableLength(variableLengthMatches)
|
||||||
|
if !ok {
|
||||||
|
mustFail = true
|
||||||
|
}
|
||||||
|
min = exMin
|
||||||
|
max = exMax
|
||||||
|
}
|
||||||
|
|
||||||
return func(value interface{}) bool {
|
return func(value interface{}) bool {
|
||||||
|
// preprocessing error
|
||||||
|
if mustFail {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// check type
|
// check type
|
||||||
strValue, isString := value.(string)
|
strValue, isString := value.(string)
|
||||||
if !isString {
|
if !isString {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check fixed length
|
if isSimpleString {
|
||||||
if fixedLengthMatches != nil {
|
return true
|
||||||
|
|
||||||
// incoherence fail
|
|
||||||
if len(fixedLengthMatches) < 2 {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract length
|
// check length against previously extracted length
|
||||||
fixedLen, err := strconv.ParseUint(fixedLengthMatches[1], 10, 64)
|
l := len(strValue)
|
||||||
if err != nil {
|
return l >= min && l <= max
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check against value
|
|
||||||
return len(strValue) == int(fixedLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check variable length
|
|
||||||
if variableLengthMatches != nil {
|
|
||||||
|
|
||||||
minLen, maxLen, ok := s.getVariableLength(variableLengthMatches)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check against value
|
|
||||||
return len(strValue) >= minLen && len(strValue) <= maxLen
|
|
||||||
}
|
|
||||||
|
|
||||||
// if should NEVER be here ; so fail
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,8 +83,8 @@ func (String) getFixedLength(regexMatches []string) (int, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract length
|
// extract length
|
||||||
fixedLength, err := strconv.ParseUint(regexMatches[1], 10, 64)
|
fixedLength, err := strconv.ParseInt(regexMatches[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil || fixedLength < 0 {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,13 +99,13 @@ func (String) getVariableLength(regexMatches []string) (int, int, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract minimum length
|
// extract minimum length
|
||||||
minLen, err := strconv.ParseUint(regexMatches[1], 10, 64)
|
minLen, err := strconv.ParseInt(regexMatches[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil || minLen < 0 {
|
||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
// extract maximum length
|
// extract maximum length
|
||||||
maxLen, err := strconv.ParseUint(regexMatches[2], 10, 64)
|
maxLen, err := strconv.ParseInt(regexMatches[2], 10, 64)
|
||||||
if err != nil {
|
if err != nil || maxLen < 0 {
|
||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
package builtin_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.xdrm.io/go/aicra/typecheck/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestString_New(t *testing.T) {
|
||||||
|
inst := interface{}(builtin.NewString())
|
||||||
|
|
||||||
|
switch cast := inst.(type) {
|
||||||
|
case *builtin.String:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Errorf("expect %T ; got %T", &builtin.String{}, cast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestString_AvailableTypes(t *testing.T) {
|
||||||
|
|
||||||
|
inst := builtin.NewString()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Type string
|
||||||
|
Handled bool
|
||||||
|
}{
|
||||||
|
{"string", true},
|
||||||
|
{"String", false},
|
||||||
|
{"STRING", false},
|
||||||
|
{" string", false},
|
||||||
|
{"string ", false},
|
||||||
|
{" string ", false},
|
||||||
|
|
||||||
|
{"string(1)", true},
|
||||||
|
{"string( 1)", false},
|
||||||
|
{"string(1 )", false},
|
||||||
|
{"string( 1 )", false},
|
||||||
|
|
||||||
|
{"string(1,2)", true},
|
||||||
|
{"string(1, 2)", true},
|
||||||
|
{"string(1, 2)", false},
|
||||||
|
{"string( 1,2)", false},
|
||||||
|
{"string(1,2 )", false},
|
||||||
|
{"string( 1,2 )", false},
|
||||||
|
{"string( 1, 2)", false},
|
||||||
|
{"string(1, 2 )", false},
|
||||||
|
{"string( 1, 2 )", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
checker := inst.Checker(test.Type)
|
||||||
|
|
||||||
|
if checker == nil {
|
||||||
|
if test.Handled {
|
||||||
|
t.Errorf("expect %q to be handled", test.Type)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !test.Handled {
|
||||||
|
t.Errorf("expect %q NOT to be handled", test.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestString_AnyLength(t *testing.T) {
|
||||||
|
const typeName = "string"
|
||||||
|
|
||||||
|
checker := builtin.NewString().Checker(typeName)
|
||||||
|
if checker == nil {
|
||||||
|
t.Errorf("expect %q to be handled", typeName)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Value interface{}
|
||||||
|
Valid bool
|
||||||
|
}{
|
||||||
|
{"string", true},
|
||||||
|
{[]byte("bytes"), false},
|
||||||
|
{1, false},
|
||||||
|
{0.1, false},
|
||||||
|
{nil, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
if checker(test.Value) {
|
||||||
|
if !test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be invalid", i)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be valid", i)
|
||||||
|
t.Fail()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestString_FixedLength(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Type string
|
||||||
|
Value interface{}
|
||||||
|
Valid bool
|
||||||
|
}{
|
||||||
|
{"string(0)", "", true},
|
||||||
|
{"string(0)", "", true},
|
||||||
|
{"string(0)", "1", false},
|
||||||
|
|
||||||
|
{"string(16)", "1234567890123456", true},
|
||||||
|
{"string(16)", "123456789012345", false},
|
||||||
|
{"string(16)", "12345678901234567", false},
|
||||||
|
|
||||||
|
{"string(1000)", string(make([]byte, 1000)), true},
|
||||||
|
{"string(1000)", string(make([]byte, 1000-1)), false},
|
||||||
|
{"string(1000)", string(make([]byte, 1000+1)), false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
checker := builtin.NewString().Checker(test.Type)
|
||||||
|
if checker == nil {
|
||||||
|
t.Errorf("%d: expect %q to be handled", i, test.Type)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if checker(test.Value) {
|
||||||
|
if !test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be invalid", i)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be valid", i)
|
||||||
|
t.Fail()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestString_VariableLength(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Type string
|
||||||
|
Value interface{}
|
||||||
|
Valid bool
|
||||||
|
}{
|
||||||
|
{"string(0,0)", "", true},
|
||||||
|
{"string(0,0)", "1", false},
|
||||||
|
|
||||||
|
{"string(0,1)", "", true},
|
||||||
|
{"string(0,1)", "1", true},
|
||||||
|
{"string(0,1)", "12", false},
|
||||||
|
|
||||||
|
{"string(5,16)", "1234", false},
|
||||||
|
{"string(5,16)", "12345", true},
|
||||||
|
{"string(5,16)", "123456", true},
|
||||||
|
{"string(5,16)", "1234567", true},
|
||||||
|
{"string(5,16)", "12345678", true},
|
||||||
|
{"string(5,16)", "123456789", true},
|
||||||
|
{"string(5,16)", "1234567890", true},
|
||||||
|
{"string(5,16)", "12345678901", true},
|
||||||
|
{"string(5,16)", "123456789012", true},
|
||||||
|
{"string(5,16)", "1234567890123", true},
|
||||||
|
{"string(5,16)", "12345678901234", true},
|
||||||
|
{"string(5,16)", "123456789012345", true},
|
||||||
|
{"string(5,16)", "1234567890123456", true},
|
||||||
|
{"string(5,16)", "12345678901234567", false},
|
||||||
|
|
||||||
|
{"string(999,1000)", string(make([]byte, 998)), false},
|
||||||
|
{"string(999,1000)", string(make([]byte, 999)), true},
|
||||||
|
{"string(999,1000)", string(make([]byte, 1000)), true},
|
||||||
|
{"string(999,1000)", string(make([]byte, 1001)), false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
checker := builtin.NewString().Checker(test.Type)
|
||||||
|
if checker == nil {
|
||||||
|
t.Errorf("%d: expect %q to be handled", i, test.Type)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if checker(test.Value) {
|
||||||
|
if !test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be invalid", i)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if test.Valid {
|
||||||
|
t.Errorf("%d: expect value to be valid", i)
|
||||||
|
t.Fail()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue