Skip to content

Commit

Permalink
Merge pull request #11 from go-carrot/br.type-handlers
Browse files Browse the repository at this point in the history
Switch over to type handlers
  • Loading branch information
BrandonRomano authored Jun 22, 2017
2 parents 888e43e + 8009ad9 commit 141ed2c
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 99 deletions.
59 changes: 53 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,28 @@ Let's check out the Value struct.

```go
type Value struct {
Result interface{}
Name string
Input string
Rules []Rule
Result interface{}
Default string
Name string
Input string
Rules []Rule
TypeHandler TypeHandler
}
```

#### Result

Result must be a pointer to the variable you want to store the parsed input in.

Valid types for this are `*string`, `*float32`, `*float64`, `*bool`, `*int`, `*int8`, `*int16`, `*int32`, `*int64`, `*uint`, `*uint8`, `*uint16`, `*uint32`, `*uint64`.
By default, supported types for this are `*string`, `*float32`, `*float64`, `*bool`, `*int`, `*int8`, `*int16`, `*int32`, `*int64`, `*uint`, `*uint8`, `*uint16`, `*uint32`, `*uint64`.

It is expected that the value of the `Input` parameter can be parsed into the decided type using their respective [strconv](https://golang.org/pkg/strconv/) function, else an error will be thrown by [the Validate function](#the-validate-function) when it is called.
For the default supported types, it is expected that the value of the `Input` parameter can be parsed into the decided type using their respective [strconv](https://golang.org/pkg/strconv/) function, else an error will be thrown by [the Validate function](#the-validate-function) when it is called.

If you need to use another type, `TypeHandler` must also be set to the Value struct.

#### Default

This is the optional default value of `Input` that will be set, if the value of `Input` ends up being an empty string.

#### Name

Expand All @@ -70,6 +78,12 @@ This is a slice of rules that you require a particular value to pass.

This is optional, and can be not set if you don't have any rules for your value to pass. The value will still go through the type check if the Input is a non-empty string.

#### TypeHandler

TypeHandler is a function that defines how the input string is parsed.

For basic types, it's not necessary to implement your own TypeHandler, as they have already been implemented and will be attached to Values automatically.

## Rules

A Rule is a very simple type of function:
Expand Down Expand Up @@ -113,6 +127,39 @@ Both of these strategies should feel very fluent in use:

> You won't find any prebuilt rules in [go-carrot/validator](https://github.com/go-carrot/validator). If you're looking for those check out the [go-carrot/rules](https://github.com/go-carrot/rules) repository.
## TypeHandlers

A TypeHandler is a function that follows the following definition:

```go
type TypeHandler func(input string, value *Value) error
```

A TypeHandler is responsible for:

- Validation of the non-null string input
- (TypeHandlers aren't called if the string is not set - you can mandate that a value is required via `Rules`)
- Converting string input to the desired type
- Passing the converted type into the `value.Result`

TypeHandlers are best explained by example. This is a TypeHandler for a `*sql.NullInt64`

```go
func NullInt64TypeHandler = func(input string, value *v.Value) error {
// Get int64
res, err := strconv.ParseInt(input, 10, 64)
if err != nil {
return errors.New("Invalid parameter, must be an int64")
}

// Update nullInt
nullInt := value.Result.(*sql.NullInt64)
(*nullInt).Int64 = int64(res)
(*nullInt).Valid = true
return nil
}
```

## The Validate Function

The validate function is the function that will actually perform your input validation. This function will throw an error if any of your values fail validation.
Expand Down
133 changes: 133 additions & 0 deletions type_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package validator

import (
"errors"
"fmt"
"strconv"
)

func stringHandler(input string, value *Value) error {
*value.Result.(*string) = input
return nil
}

func float32Handler(input string, value *Value) error {
res, err := strconv.ParseFloat(input, 32)
if err != nil {
return errors.New(invalidParam(value.Name, "a float32"))
}
*value.Result.(*float32) = float32(res)
return nil
}

func float64Handler(input string, value *Value) error {
res, err := strconv.ParseFloat(input, 64)
if err != nil {
return errors.New(invalidParam(value.Name, "a float64"))
}
*value.Result.(*float64) = float64(res)
return nil
}

func boolHandler(input string, value *Value) error {
res, err := strconv.ParseBool(input)
if err != nil {
return errors.New(invalidParam(value.Name, "a bool"))
}
*value.Result.(*bool) = res
return nil
}

func intHandler(input string, value *Value) error {
res, err := strconv.ParseInt(input, 10, 0)
if err != nil {
return errors.New(invalidParam(value.Name, "an int"))
}
*value.Result.(*int) = int(res)
return nil
}

func int8Handler(input string, value *Value) error {
res, err := strconv.ParseInt(input, 10, 8)
if err != nil {
return errors.New(invalidParam(value.Name, "an int8"))
}
*value.Result.(*int8) = int8(res)
return nil
}

func int16Handler(input string, value *Value) error {
res, err := strconv.ParseInt(input, 10, 16)
if err != nil {
return errors.New(invalidParam(value.Name, "an int16"))
}
*value.Result.(*int16) = int16(res)
return nil
}

func int32Handler(input string, value *Value) error {
res, err := strconv.ParseInt(input, 10, 32)
if err != nil {
return errors.New(invalidParam(value.Name, "an int32"))
}
*value.Result.(*int32) = int32(res)
return nil
}

func int64Handler(input string, value *Value) error {
res, err := strconv.ParseInt(input, 10, 64)
if err != nil {
return errors.New(invalidParam(value.Name, "an int64"))
}
*value.Result.(*int64) = int64(res)
return nil
}

func uintHandler(input string, value *Value) error {
res, err := strconv.ParseUint(input, 10, 0)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint"))
}
*value.Result.(*uint) = uint(res)
return nil
}

func uint8Handler(input string, value *Value) error {
res, err := strconv.ParseUint(input, 10, 8)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint8"))
}
*value.Result.(*uint8) = uint8(res)
return nil
}

func uint16Handler(input string, value *Value) error {
res, err := strconv.ParseUint(input, 10, 16)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint16"))
}
*value.Result.(*uint16) = uint16(res)
return nil
}

func uint32Handler(input string, value *Value) error {
res, err := strconv.ParseUint(input, 10, 32)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint32"))
}
*value.Result.(*uint32) = uint32(res)
return nil
}

func uint64Handler(input string, value *Value) error {
res, err := strconv.ParseUint(input, 10, 64)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint64"))
}
*value.Result.(*uint64) = uint64(res)
return nil
}

func invalidParam(name string, mustBe string) string {
return fmt.Sprintf("Invalid `%v` parameter, `%v` must be %v", name, name, mustBe)
}
151 changes: 58 additions & 93 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import (
"errors"
"fmt"
"reflect"
"strconv"
)

// Value is the definition of a parameter that you would like to perform validation against.
type Value struct {
Result interface{}
Default string
Name string
Input string
Rules []Rule
Result interface{}
Default string
Name string
Input string
Rules []Rule
TypeHandler TypeHandler
}

// TypeHandler is a function that is responsible for
// are responsible for validating the input
// matches the type, and also to stuff the result into the *value.Result
type TypeHandler func(input string, value *Value) error

// Rule is a function that defines logic you would expect a Value to pass.
// The parameters passed in to this function are their respective
// values as they were set in the Value struct.
Expand All @@ -40,97 +45,57 @@ func Validate(values []*Value) error {
}
}

// Sticking the value of result into result
if resolvedInput != "" {
switch i := (value.Result).(type) {
default:
panic(fmt.Sprintf("go-carrot/validator cannot handle a Value with Result of type %v", reflect.TypeOf(i)))
case *string:
*i = resolvedInput
case *float32:
res, err := strconv.ParseFloat(resolvedInput, 32)
if err != nil {
return errors.New(invalidParam(value.Name, "a float32"))
}
*i = float32(res)
case *float64:
res, err := strconv.ParseFloat(resolvedInput, 64)
if err != nil {
return errors.New(invalidParam(value.Name, "a float64"))
}
*i = float64(res)
case *bool:
res, err := strconv.ParseBool(resolvedInput)
if err != nil {
return errors.New(invalidParam(value.Name, "a bool"))
}
*i = res
case *int:
res, err := strconv.ParseInt(resolvedInput, 10, 0)
if err != nil {
return errors.New(invalidParam(value.Name, "an int"))
}
*i = int(res)
case *int8:
res, err := strconv.ParseInt(resolvedInput, 10, 8)
if err != nil {
return errors.New(invalidParam(value.Name, "an int8"))
}
*i = int8(res)
case *int16:
res, err := strconv.ParseInt(resolvedInput, 10, 16)
if err != nil {
return errors.New(invalidParam(value.Name, "an int16"))
}
*i = int16(res)
case *int32:
res, err := strconv.ParseInt(resolvedInput, 10, 32)
if err != nil {
return errors.New(invalidParam(value.Name, "an int32"))
}
*i = int32(res)
case *int64:
res, err := strconv.ParseInt(resolvedInput, 10, 64)
if err != nil {
return errors.New(invalidParam(value.Name, "an int64"))
}
*i = int64(res)
case *uint:
res, err := strconv.ParseUint(resolvedInput, 10, 0)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint"))
}
*i = uint(res)
case *uint8:
res, err := strconv.ParseUint(resolvedInput, 10, 8)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint8"))
}
*i = uint8(res)
case *uint16:
res, err := strconv.ParseUint(resolvedInput, 10, 16)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint16"))
}
*i = uint16(res)
case *uint32:
res, err := strconv.ParseUint(resolvedInput, 10, 32)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint32"))
}
*i = uint32(res)
case *uint64:
res, err := strconv.ParseUint(resolvedInput, 10, 64)
if err != nil {
return errors.New(invalidParam(value.Name, "a uint64"))
}
*i = uint64(res)
// Set primitive type handlers
if value.TypeHandler == nil {
err := applyTypeHandler(value)
if err != nil {
panic(err.Error())
}
}

// Validate against type
if value.Input != "" {
err := value.TypeHandler(resolvedInput, value)
if err != nil {
return err
}
}
}
return nil
}

func invalidParam(name string, mustBe string) string {
return fmt.Sprintf("Invalid `%v` parameter, `%v` must be %v", name, name, mustBe)
func applyTypeHandler(value *Value) error {
switch i := (value.Result).(type) {
default:
return errors.New(fmt.Sprintf("go-carrot/validator cannot by default handle a Value with Result of type %v. Must set a custom TypeHandler for %v.", reflect.TypeOf(i), value.Name))
case *string:
value.TypeHandler = stringHandler
case *float32:
value.TypeHandler = float32Handler
case *float64:
value.TypeHandler = float64Handler
case *bool:
value.TypeHandler = boolHandler
case *int:
value.TypeHandler = intHandler
case *int8:
value.TypeHandler = int8Handler
case *int16:
value.TypeHandler = int16Handler
case *int32:
value.TypeHandler = int32Handler
case *int64:
value.TypeHandler = int64Handler
case *uint:
value.TypeHandler = uintHandler
case *uint8:
value.TypeHandler = uint8Handler
case *uint16:
value.TypeHandler = uint16Handler
case *uint32:
value.TypeHandler = uint32Handler
case *uint64:
value.TypeHandler = uint64Handler
}
return nil
}
Loading

0 comments on commit 141ed2c

Please sign in to comment.