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
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"encoding/json"
|
||||
|
||||
"git.xdrm.io/go/aicra/typecheck"
|
||||
)
|
||||
|
@ -41,7 +41,8 @@ func readFloat(value interface{}) (float64, bool) {
|
|||
|
||||
// serialized string -> try to convert to float
|
||||
case string:
|
||||
floatVal, err := strconv.ParseFloat(cast, 64)
|
||||
num := json.Number(cast)
|
||||
floatVal, err := num.Float64()
|
||||
return floatVal, err == nil
|
||||
|
||||
// unknown type
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
|
||||
"git.xdrm.io/go/aicra/typecheck"
|
||||
)
|
||||
|
||||
|
@ -19,12 +22,37 @@ func (Int) Checker(typeName string) typecheck.CheckerFunc {
|
|||
return nil
|
||||
}
|
||||
return func(value interface{}) bool {
|
||||
cast, isFloat := readFloat(value)
|
||||
_, isInt := readInt(value)
|
||||
|
||||
if !isFloat {
|
||||
return false
|
||||
return isInt
|
||||
}
|
||||
}
|
||||
|
||||
return cast == float64(int(cast))
|
||||
// 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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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 {
|
||||
// preprocessing error
|
||||
if mustFail {
|
||||
return false
|
||||
}
|
||||
|
||||
// check type
|
||||
strValue, isString := value.(string)
|
||||
if !isString {
|
||||
return false
|
||||
}
|
||||
|
||||
// check fixed length
|
||||
if fixedLengthMatches != nil {
|
||||
|
||||
// incoherence fail
|
||||
if len(fixedLengthMatches) < 2 {
|
||||
return false
|
||||
if isSimpleString {
|
||||
return true
|
||||
}
|
||||
|
||||
// extract length
|
||||
fixedLen, err := strconv.ParseUint(fixedLengthMatches[1], 10, 64)
|
||||
if err != nil {
|
||||
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
|
||||
// check length against previously extracted length
|
||||
l := len(strValue)
|
||||
return l >= min && l <= max
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,8 +83,8 @@ func (String) getFixedLength(regexMatches []string) (int, bool) {
|
|||
}
|
||||
|
||||
// extract length
|
||||
fixedLength, err := strconv.ParseUint(regexMatches[1], 10, 64)
|
||||
if err != nil {
|
||||
fixedLength, err := strconv.ParseInt(regexMatches[1], 10, 64)
|
||||
if err != nil || fixedLength < 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
|
@ -95,13 +99,13 @@ func (String) getVariableLength(regexMatches []string) (int, int, bool) {
|
|||
}
|
||||
|
||||
// extract minimum length
|
||||
minLen, err := strconv.ParseUint(regexMatches[1], 10, 64)
|
||||
if err != nil {
|
||||
minLen, err := strconv.ParseInt(regexMatches[1], 10, 64)
|
||||
if err != nil || minLen < 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
// extract maximum length
|
||||
maxLen, err := strconv.ParseUint(regexMatches[2], 10, 64)
|
||||
if err != nil {
|
||||
maxLen, err := strconv.ParseInt(regexMatches[2], 10, 64)
|
||||
if err != nil || maxLen < 0 {
|
||||
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