From a83d0775690df73b63b07c4a7f99d859b614752a Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 2 May 2019 22:15:03 +0200 Subject: [PATCH] feat: add builtin types [int, uint] | fix request param getters | add request param getters [GetFloat, GetInt, GetUint, GetStrings] with permissive possible types and 'automatic' conversion --- api/request.param.go | 130 +++++++++++++++++++++++++++++++++-- typecheck/builtin/any.go | 2 +- typecheck/builtin/bool.go | 2 +- typecheck/builtin/float64.go | 49 +++++++------ typecheck/builtin/int.go | 30 ++++++++ typecheck/builtin/string.go | 3 +- typecheck/builtin/uint.go | 30 ++++++++ typecheck/type.go | 6 +- 8 files changed, 220 insertions(+), 32 deletions(-) create mode 100644 typecheck/builtin/int.go create mode 100644 typecheck/builtin/uint.go diff --git a/api/request.param.go b/api/request.param.go index 05146d6..57854b2 100644 --- a/api/request.param.go +++ b/api/request.param.go @@ -1,5 +1,9 @@ package api +import ( + "fmt" +) + // ConstError is a wrapper to set constant errors type ConstError string @@ -26,17 +30,133 @@ func (rp RequestParam) Get(key string) (interface{}, error) { return rawValue, nil } -// GetString returns a string and an error if not found or string +// GetString returns a string and an error if not found or invalid type func (rp RequestParam) GetString(key string) (string, error) { rawValue, err := rp.Get(key) if err != nil { return "", err } - convertedValue, canConvert := rawValue.(string) - if !canConvert { + switch cast := rawValue.(type) { + case fmt.Stringer: + return cast.String(), nil + case []byte: + return string(cast), nil + case string: + return cast, nil + default: return "", ErrReqParamNotType } - - return convertedValue, nil +} + +// GetFloat returns a float64 and an error if not found or invalid type +func (rp RequestParam) GetFloat(key string) (float64, error) { + rawValue, err := rp.Get(key) + if err != nil { + return 0, err + } + + switch cast := rawValue.(type) { + case float32: + return float64(cast), nil + case float64: + return cast, nil + case int, int8, int16, int32, int64: + intVal, ok := cast.(int) + if !ok || intVal != int(float64(intVal)) { + return 0, ErrReqParamNotType + } + return float64(intVal), nil + case uint, uint8, uint16, uint32, uint64: + uintVal, ok := cast.(uint) + if !ok || uintVal != uint(float64(uintVal)) { + return 0, ErrReqParamNotType + } + return float64(uintVal), nil + default: + return 0, ErrReqParamNotType + } +} + +// GetInt returns an int and an error if not found or invalid type +func (rp RequestParam) GetInt(key string) (int, error) { + rawValue, err := rp.Get(key) + if err != nil { + return 0, err + } + + switch cast := rawValue.(type) { + case float32, float64: + floatVal, ok := cast.(float64) + if !ok || floatVal < 0 || floatVal != float64(int(floatVal)) { + return 0, ErrReqParamNotType + } + return int(floatVal), nil + case int, int8, int16, int32, int64: + intVal, ok := cast.(int) + if !ok || intVal != int(int(intVal)) { + return 0, ErrReqParamNotType + } + return int(intVal), nil + default: + return 0, ErrReqParamNotType + } +} + +// GetUint returns an uint and an error if not found or invalid type +func (rp RequestParam) GetUint(key string) (uint, error) { + rawValue, err := rp.Get(key) + if err != nil { + return 0, err + } + + switch cast := rawValue.(type) { + case float32, float64: + floatVal, ok := cast.(float64) + if !ok || floatVal < 0 || floatVal != float64(uint(floatVal)) { + return 0, ErrReqParamNotType + } + return uint(floatVal), nil + case int, int8, int16, int32, int64: + intVal, ok := cast.(int) + if !ok || intVal != int(uint(intVal)) { + return 0, ErrReqParamNotType + } + return uint(intVal), nil + case uint, uint8, uint16, uint32, uint64: + uintVal, ok := cast.(uint) + if !ok { + return 0, ErrReqParamNotType + } + return uintVal, nil + default: + return 0, ErrReqParamNotType + } +} + +// GetStrings returns an []slice and an error if not found or invalid type +func (rp RequestParam) GetStrings(key string) ([]string, error) { + rawValue, err := rp.Get(key) + if err != nil { + return nil, err + } + + switch cast := rawValue.(type) { + case []fmt.Stringer: + strings := make([]string, len(cast)) + for i, stringer := range cast { + strings[i] = stringer.String() + } + return strings, nil + case [][]byte: + strings := make([]string, len(cast)) + for i, bytes := range cast { + strings[i] = string(bytes) + } + return strings, nil + case []string: + return cast, nil + default: + return nil, ErrReqParamNotType + } } diff --git a/typecheck/builtin/any.go b/typecheck/builtin/any.go index e56a886..9be7254 100644 --- a/typecheck/builtin/any.go +++ b/typecheck/builtin/any.go @@ -11,7 +11,7 @@ func NewAny() *Any { } // Checker returns the checker function -func (Any) Checker(typeName string) typecheck.Checker { +func (Any) Checker(typeName string) typecheck.CheckerFunc { // nothing if type not handled if typeName != "any" { return nil diff --git a/typecheck/builtin/bool.go b/typecheck/builtin/bool.go index 0fdc483..bf35dd7 100644 --- a/typecheck/builtin/bool.go +++ b/typecheck/builtin/bool.go @@ -11,7 +11,7 @@ func NewBool() *Bool { } // Checker returns the checker function -func (Bool) Checker(typeName string) typecheck.Checker { +func (Bool) Checker(typeName string) typecheck.CheckerFunc { // nothing if type not handled if typeName != "bool" { return nil diff --git a/typecheck/builtin/float64.go b/typecheck/builtin/float64.go index 8e8e12a..912b97b 100644 --- a/typecheck/builtin/float64.go +++ b/typecheck/builtin/float64.go @@ -1,7 +1,6 @@ package builtin import ( - "log" "strconv" "git.xdrm.io/go/aicra/typecheck" @@ -16,30 +15,38 @@ func NewFloat64() *Float64 { } // Checker returns the checker function -func (Float64) Checker(typeName string) typecheck.Checker { +func (Float64) Checker(typeName string) typecheck.CheckerFunc { // nothing if type not handled if typeName != "float64" && typeName != "float" { return nil } return func(value interface{}) bool { - strVal, isString := value.(string) - _, isFloat64 := value.(float64) - - log.Printf("1") - - // raw float - if isFloat64 { - return true - } - - // string float - if !isString { - return false - } - _, err := strconv.ParseFloat(strVal, 64) - if err != nil { - return false - } - return true + _, isFloat := readFloat(value) + return isFloat + } +} + +// readFloat tries to read a serialized float and returns whether it succeeded. +func readFloat(value interface{}) (float64, bool) { + switch cast := value.(type) { + + case int: + return float64(cast), true + + case uint: + return float64(cast), true + + case float64: + return cast, true + + // serialized string -> try to convert to float + case string: + floatVal, err := strconv.ParseFloat(cast, 64) + return floatVal, err == nil + + // unknown type + default: + return 0, false + } } diff --git a/typecheck/builtin/int.go b/typecheck/builtin/int.go new file mode 100644 index 0000000..2e5ced9 --- /dev/null +++ b/typecheck/builtin/int.go @@ -0,0 +1,30 @@ +package builtin + +import ( + "git.xdrm.io/go/aicra/typecheck" +) + +// Int checks if a value is an int +type Int struct{} + +// NewInt returns a bare number type checker +func NewInt() *Int { + return &Int{} +} + +// Checker returns the checker function +func (Int) Checker(typeName string) typecheck.CheckerFunc { + // nothing if type not handled + if typeName != "int" { + return nil + } + return func(value interface{}) bool { + cast, isFloat := readFloat(value) + + if !isFloat { + return false + } + + return cast == float64(int(cast)) + } +} diff --git a/typecheck/builtin/string.go b/typecheck/builtin/string.go index 9ae24ab..ef2c6b2 100644 --- a/typecheck/builtin/string.go +++ b/typecheck/builtin/string.go @@ -19,7 +19,7 @@ func NewString() *String { } // Checker returns the checker function. Availables type names are : `string`, `string(length)` and `string(minLength, maxLength)`. -func (s String) Checker(typeName string) typecheck.Checker { +func (s String) Checker(typeName string) typecheck.CheckerFunc { isSimpleString := typeName == "string" fixedLengthMatches := fixedLengthRegex.FindStringSubmatch(typeName) variableLengthMatches := variableLengthRegex.FindStringSubmatch(typeName) @@ -38,6 +38,7 @@ func (s String) Checker(typeName string) typecheck.Checker { // check fixed length if fixedLengthMatches != nil { + // incoherence fail if len(fixedLengthMatches) < 2 { return false diff --git a/typecheck/builtin/uint.go b/typecheck/builtin/uint.go new file mode 100644 index 0000000..2eb7914 --- /dev/null +++ b/typecheck/builtin/uint.go @@ -0,0 +1,30 @@ +package builtin + +import ( + "git.xdrm.io/go/aicra/typecheck" +) + +// Uint checks if a value is an uint +type Uint struct{} + +// NewUint returns a bare number type checker +func NewUint() *Uint { + return &Uint{} +} + +// Checker returns the checker function +func (Uint) Checker(typeName string) typecheck.CheckerFunc { + // nothing if type not handled + if typeName != "uint" { + return nil + } + return func(value interface{}) bool { + cast, isFloat := readFloat(value) + + if !isFloat { + return false + } + + return cast >= 0 && cast == float64(int(cast)) + } +} diff --git a/typecheck/type.go b/typecheck/type.go index a278091..0a34e49 100644 --- a/typecheck/type.go +++ b/typecheck/type.go @@ -8,11 +8,11 @@ var ErrNoMatchingType = errors.New("no matching type") // ErrDoesNotMatch when the value is invalid var ErrDoesNotMatch = errors.New("does not match") -// Checker returns whether a given value fulfills a type -type Checker func(interface{}) bool +// CheckerFunc returns whether a given value fulfills a type +type CheckerFunc func(interface{}) bool // Type represents a type checker type Type interface { // given a type name, returns the checker function or NIL if the type is not handled here - Checker(string) Checker + Checker(string) CheckerFunc }