diff --git a/typecheck/builtin/bool.go b/typecheck/builtin/bool.go index bf35dd7..2d5ae07 100644 --- a/typecheck/builtin/bool.go +++ b/typecheck/builtin/bool.go @@ -17,7 +17,28 @@ func (Bool) Checker(typeName string) typecheck.CheckerFunc { return nil } return func(value interface{}) bool { - _, isBool := value.(bool) + _, isBool := readBool(value) return isBool } } + +// readBool tries to read a serialized boolean and returns whether it succeeded. +func readBool(value interface{}) (bool, bool) { + switch cast := value.(type) { + case bool: + return cast, true + + case string: + strVal := string(cast) + return strVal == "true", strVal == "true" || strVal == "false" + + case []byte: + strVal := string(cast) + return strVal == "true", strVal == "true" || strVal == "false" + + default: + return false, false + } + + return false, false +} diff --git a/typecheck/builtin/bool_test.go b/typecheck/builtin/bool_test.go new file mode 100644 index 0000000..8e63344 --- /dev/null +++ b/typecheck/builtin/bool_test.go @@ -0,0 +1,115 @@ +package builtin_test + +import ( + "fmt" + "testing" + + "git.xdrm.io/go/aicra/typecheck/builtin" +) + +func TestBool_New(t *testing.T) { + t.Parallel() + + inst := interface{}(builtin.NewBool()) + + switch cast := inst.(type) { + case *builtin.Bool: + return + default: + t.Errorf("expect %T ; got %T", &builtin.Bool{}, cast) + } +} + +func TestBool_AvailableTypes(t *testing.T) { + t.Parallel() + + inst := builtin.NewBool() + + tests := []struct { + Type string + Handled bool + }{ + {"bool", true}, + {"Bool", false}, + {"boolean", false}, + {" bool", false}, + {"bool ", false}, + {" bool ", false}, + } + + for _, test := range tests { + t.Run(test.Type, func(t *testing.T) { + checker := inst.Checker(test.Type) + if checker == nil { + if test.Handled { + t.Errorf("expect %q to be handled", test.Type) + t.Fail() + } + return + } + + if !test.Handled { + t.Errorf("expect %q NOT to be handled", test.Type) + t.Fail() + } + }) + } + +} + +func TestBool_Values(t *testing.T) { + t.Parallel() + + const typeName = "bool" + + checker := builtin.NewBool().Checker(typeName) + if checker == nil { + t.Errorf("expect %q to be handled", typeName) + t.Fail() + } + + tests := []struct { + Value interface{} + Valid bool + }{ + {true, true}, + {false, true}, + {1, false}, + {0, false}, + {-1, false}, + + // json number + {"-1", false}, + {"0", false}, + {"1", false}, + + // json string + {"true", true}, + {"false", true}, + {[]byte("true"), true}, + {[]byte("false"), true}, + + {"string", false}, + {[]byte("bytes"), false}, + {-0.1, false}, + {0.1, false}, + {nil, false}, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + if checker(test.Value) { + if !test.Valid { + t.Errorf("expect value to be invalid") + t.Fail() + } + return + } + if test.Valid { + t.Errorf("expect value to be valid") + t.Fail() + } + }) + } + +} diff --git a/typecheck/builtin/float64.go b/typecheck/builtin/float64.go index 7dc849c..081ac06 100644 --- a/typecheck/builtin/float64.go +++ b/typecheck/builtin/float64.go @@ -45,6 +45,11 @@ func readFloat(value interface{}) (float64, bool) { floatVal, err := num.Float64() return floatVal, err == nil + case []byte: + num := json.Number(cast) + floatVal, err := num.Float64() + return floatVal, err == nil + // unknown type default: return 0, false diff --git a/typecheck/builtin/int.go b/typecheck/builtin/int.go index 806838f..a64c0df 100644 --- a/typecheck/builtin/int.go +++ b/typecheck/builtin/int.go @@ -49,6 +49,12 @@ func readInt(value interface{}) (int, bool) { num := json.Number(cast) intVal, err := num.Int64() return int(intVal), err == nil + // serialized string -> try to convert to float + + case []byte: + num := json.Number(cast) + intVal, err := num.Int64() + return int(intVal), err == nil // unknown type default: diff --git a/typecheck/builtin/string.go b/typecheck/builtin/string.go index 1c9ae19..02501a1 100644 --- a/typecheck/builtin/string.go +++ b/typecheck/builtin/string.go @@ -61,6 +61,12 @@ func (s String) Checker(typeName string) typecheck.CheckerFunc { // check type strValue, isString := value.(string) + byteSliceValue, isByteSlice := value.([]byte) + if !isString && isByteSlice { + strValue = string(byteSliceValue) + isString = true + } + if !isString { return false } diff --git a/typecheck/builtin/string_test.go b/typecheck/builtin/string_test.go index 3a0912f..048485b 100644 --- a/typecheck/builtin/string_test.go +++ b/typecheck/builtin/string_test.go @@ -87,7 +87,7 @@ func TestString_AnyLength(t *testing.T) { Valid bool }{ {"string", true}, - {[]byte("bytes"), false}, + {[]byte("bytes"), true}, {1, false}, {0.1, false}, {nil, false}, diff --git a/typecheck/builtin/uint.go b/typecheck/builtin/uint.go index fbbaf38..7feb9a4 100644 --- a/typecheck/builtin/uint.go +++ b/typecheck/builtin/uint.go @@ -53,6 +53,15 @@ func readUint(value interface{}) (uint, bool) { overflows := floatVal < 0 || floatVal > math.MaxUint64 return uint(floatVal), !overflows + case []byte: + num := json.Number(cast) + floatVal, err := num.Float64() + if err != nil { + return 0, false + } + overflows := floatVal < 0 || floatVal > math.MaxUint64 + return uint(floatVal), !overflows + // unknown type default: return 0, false