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

This commit is contained in:
Adrien Marquès 2019-05-02 22:15:03 +02:00
parent eb5ce4c0d0
commit a83d077569
8 changed files with 220 additions and 32 deletions

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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
}
}

30
typecheck/builtin/int.go Normal file
View File

@ -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))
}
}

View File

@ -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

30
typecheck/builtin/uint.go Normal file
View File

@ -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))
}
}

View File

@ -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
}