mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
NEW (HOT) FEATURE: Add custom error handlers on path type parameters error
This commit is contained in:
parent
567c06702f
commit
5990e7f432
16
HISTORY.md
16
HISTORY.md
|
@ -28,6 +28,22 @@ The codebase for Dependency Injection, Internationalization and localization and
|
||||||
|
|
||||||
## Fixes and Improvements
|
## Fixes and Improvements
|
||||||
|
|
||||||
|
- **New feature:** add the ability to set custom error handlers on path type parameters errors (existing or custom ones). Example Code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
app.Macros().Get("uuid").HandleError(func(ctx iris.Context, err error) {
|
||||||
|
ctx.StatusCode(iris.StatusBadRequest)
|
||||||
|
ctx.JSON(iris.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
"message": "invalid path parameter",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/users/{id:uuid}", getUser)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Improve the performance and fix `:int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64` path type parameters couldn't accept a positive number written with the plus symbol or with a leading zeroes, e.g. `+42` and `021`.
|
||||||
|
|
||||||
- The `iris.WithEmptyFormError` option is respected on `context.ReadQuery` method too, as requested at [#1727](https://github.com/kataras/iris/issues/1727). [Example comments](https://github.com/kataras/iris/blob/master/_examples/request-body/read-query/main.go) were updated.
|
- The `iris.WithEmptyFormError` option is respected on `context.ReadQuery` method too, as requested at [#1727](https://github.com/kataras/iris/issues/1727). [Example comments](https://github.com/kataras/iris/blob/master/_examples/request-body/read-query/main.go) were updated.
|
||||||
|
|
||||||
- New `httptest.Strict` option setter to enable the `httpexpect.RequireReporter` instead of the default `httpexpect.AssetReporter. Use that to enable complete test failure on the first error. As requested at: [#1722](https://github.com/kataras/iris/issues/1722).
|
- New `httptest.Strict` option setter to enable the `httpexpect.RequireReporter` instead of the default `httpexpect.AssetReporter. Use that to enable complete test failure on the first error. As requested at: [#1722](https://github.com/kataras/iris/issues/1722).
|
||||||
|
|
|
@ -152,6 +152,15 @@ func main() {
|
||||||
// +------------------------+
|
// +------------------------+
|
||||||
// UUIDv4 (and v1) path parameter validation.
|
// UUIDv4 (and v1) path parameter validation.
|
||||||
|
|
||||||
|
// Optionally, set custom handler on path parameter type error:
|
||||||
|
app.Macros().Get("uuid").HandleError(func(ctx iris.Context, err error) {
|
||||||
|
ctx.StatusCode(iris.StatusBadRequest)
|
||||||
|
ctx.JSON(iris.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
"message": "invalid path parameter",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// http://localhost:8080/user/bb4f33e4-dc08-40d8-9f2b-e8b2bb615c0e -> OK
|
// http://localhost:8080/user/bb4f33e4-dc08-40d8-9f2b-e8b2bb615c0e -> OK
|
||||||
// http://localhost:8080/user/dsadsa-invalid-uuid -> NOT FOUND
|
// http://localhost:8080/user/dsadsa-invalid-uuid -> NOT FOUND
|
||||||
app.Get("/user/{id:uuid}", func(ctx iris.Context) {
|
app.Get("/user/{id:uuid}", func(ctx iris.Context) {
|
||||||
|
|
|
@ -3,11 +3,27 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/kataras/iris/v12/context"
|
"github.com/kataras/iris/v12/context"
|
||||||
"github.com/kataras/iris/v12/core/memstore"
|
"github.com/kataras/iris/v12/core/memstore"
|
||||||
"github.com/kataras/iris/v12/macro"
|
"github.com/kataras/iris/v12/macro"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ParamErrorHandler is a special type of Iris handler which receives
|
||||||
|
// any error produced by a path type parameter evaluator and let developers
|
||||||
|
// customize the output instead of the
|
||||||
|
// provided error code 404 or anyother status code given on the `else` literal.
|
||||||
|
//
|
||||||
|
// Note that the builtin macros return error too, but they're handled
|
||||||
|
// by the `else` literal (error code). To change this behavior
|
||||||
|
// and send a custom error response you have to register it:
|
||||||
|
// app.Macros().Get("uuid").HandleError(func(iris.Context, err error)).
|
||||||
|
// You can also set custom macros by `app.Macros().Register`.
|
||||||
|
//
|
||||||
|
// See macro.HandleError to set it.
|
||||||
|
type ParamErrorHandler = func(*context.Context, error) // alias.
|
||||||
|
|
||||||
// CanMakeHandler reports whether a macro template needs a special macro's evaluator handler to be validated
|
// CanMakeHandler reports whether a macro template needs a special macro's evaluator handler to be validated
|
||||||
// before procceed to the next handler(s).
|
// before procceed to the next handler(s).
|
||||||
// If the template does not contain any dynamic attributes and a special handler is NOT required
|
// If the template does not contain any dynamic attributes and a special handler is NOT required
|
||||||
|
@ -24,6 +40,13 @@ func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) {
|
||||||
if p.CanEval() {
|
if p.CanEval() {
|
||||||
// if at least one needs it, then create the handler.
|
// if at least one needs it, then create the handler.
|
||||||
needsMacroHandler = true
|
needsMacroHandler = true
|
||||||
|
|
||||||
|
if p.HandleError != nil {
|
||||||
|
// Check for its type.
|
||||||
|
if _, ok := p.HandleError.(ParamErrorHandler); !ok {
|
||||||
|
panic(fmt.Sprintf("HandleError must be a type of func(iris.Context, error) but got: %T", p.HandleError))
|
||||||
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +106,15 @@ func MakeFilter(tmpl macro.Template) context.Filter {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
value := p.Eval(entry.String())
|
value, passed := p.Eval(entry.String())
|
||||||
if value == nil {
|
if !passed {
|
||||||
ctx.StatusCode(p.ErrCode)
|
ctx.StatusCode(p.ErrCode) // status code can change from an error handler, set it here.
|
||||||
|
if value != nil && p.HandleError != nil {
|
||||||
|
// The "value" is an error here, always (see template.Eval).
|
||||||
|
// This is always a type of ParamErrorHandler at this state (see CanMakeHandler).
|
||||||
|
p.HandleError.(ParamErrorHandler)(ctx, value.(error))
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,12 @@ func convertBuilderFunc(fn interface{}) ParamFuncBuilder {
|
||||||
|
|
||||||
numFields := typFn.NumIn()
|
numFields := typFn.NumIn()
|
||||||
|
|
||||||
|
panicIfErr := func(i int, err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("on field index: %d: %v", i, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return func(args []string) reflect.Value {
|
return func(args []string) reflect.Value {
|
||||||
if len(args) != numFields {
|
if len(args) != numFields {
|
||||||
// no variadics support, for now.
|
// no variadics support, for now.
|
||||||
|
@ -138,12 +144,6 @@ func convertBuilderFunc(fn interface{}) ParamFuncBuilder {
|
||||||
// try to convert the string literal as we get it from the parser.
|
// try to convert the string literal as we get it from the parser.
|
||||||
var (
|
var (
|
||||||
val interface{}
|
val interface{}
|
||||||
|
|
||||||
panicIfErr = func(i int, err error) {
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("on field index: %d: %v", i, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// try to get the value based on the expected type.
|
// try to get the value based on the expected type.
|
||||||
|
@ -254,8 +254,9 @@ type (
|
||||||
master bool
|
master bool
|
||||||
trailing bool
|
trailing bool
|
||||||
|
|
||||||
Evaluator ParamEvaluator
|
Evaluator ParamEvaluator
|
||||||
funcs []ParamFunc
|
handleError interface{}
|
||||||
|
funcs []ParamFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParamFuncBuilder is a func
|
// ParamFuncBuilder is a func
|
||||||
|
@ -312,6 +313,17 @@ func (m *Macro) Trailing() bool {
|
||||||
return m.trailing
|
return m.trailing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleError registers a handler which will be executed
|
||||||
|
// when a parameter evaluator returns false and a non nil value which is a type of `error`.
|
||||||
|
// The "fnHandler" value MUST BE a type of `func(iris.Context, err error)`,
|
||||||
|
// otherwise the program will receive a panic before server startup.
|
||||||
|
// The status code of the ErrCode (`else` literal) is set
|
||||||
|
// before the error handler but it can be modified inside the handler itself.
|
||||||
|
func (m *Macro) HandleError(fnHandler interface{}) *Macro { // See handler.MakeFilter.
|
||||||
|
m.handleError = fnHandler
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// func (m *Macro) SetParamResolver(fn func(memstore.Entry) interface{}) *Macro {
|
// func (m *Macro) SetParamResolver(fn func(memstore.Entry) interface{}) *Macro {
|
||||||
// m.ParamResolver = fn
|
// m.ParamResolver = fn
|
||||||
// return m
|
// return m
|
||||||
|
|
|
@ -66,6 +66,8 @@ func TestGoodParamFuncName(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) {
|
func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if macroEvaluator.Evaluator == nil && pass {
|
if macroEvaluator.Evaluator == nil && pass {
|
||||||
return // if not evaluator defined then it should allow everything.
|
return // if not evaluator defined then it should allow everything.
|
||||||
}
|
}
|
||||||
|
@ -120,7 +122,7 @@ func TestIntEvaluatorRaw(t *testing.T) {
|
||||||
{false, "-18446744073709553213213213213213121615"}, // 5
|
{false, "-18446744073709553213213213213213121615"}, // 5
|
||||||
{false, "42 18446744073709551615"}, // 6
|
{false, "42 18446744073709551615"}, // 6
|
||||||
{false, "--42"}, // 7
|
{false, "--42"}, // 7
|
||||||
{false, "+42"}, // 8
|
{true, "+42"}, // 8
|
||||||
{false, "main.css"}, // 9
|
{false, "main.css"}, // 9
|
||||||
{false, "/assets/main.css"}, // 10
|
{false, "/assets/main.css"}, // 10
|
||||||
}
|
}
|
||||||
|
@ -130,6 +132,15 @@ func TestIntEvaluatorRaw(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkIntEvaluatorRaw(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Int.Evaluator("1234568320")
|
||||||
|
Int.Evaluator("-12345678999321")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInt8EvaluatorRaw(t *testing.T) {
|
func TestInt8EvaluatorRaw(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
pass bool
|
pass bool
|
||||||
|
@ -145,7 +156,7 @@ func TestInt8EvaluatorRaw(t *testing.T) {
|
||||||
{false, "-18446744073709553213213213213213121615"}, // 7
|
{false, "-18446744073709553213213213213213121615"}, // 7
|
||||||
{false, "42 18446744073709551615"}, // 8
|
{false, "42 18446744073709551615"}, // 8
|
||||||
{false, "--42"}, // 9
|
{false, "--42"}, // 9
|
||||||
{false, "+42"}, // 10
|
{true, "+42"}, // 10
|
||||||
{false, "main.css"}, // 11
|
{false, "main.css"}, // 11
|
||||||
{false, "/assets/main.css"}, // 12
|
{false, "/assets/main.css"}, // 12
|
||||||
}
|
}
|
||||||
|
@ -170,7 +181,7 @@ func TestInt16EvaluatorRaw(t *testing.T) {
|
||||||
{false, "-18446744073709553213213213213213121615"}, // 7
|
{false, "-18446744073709553213213213213213121615"}, // 7
|
||||||
{false, "42 18446744073709551615"}, // 8
|
{false, "42 18446744073709551615"}, // 8
|
||||||
{false, "--42"}, // 9
|
{false, "--42"}, // 9
|
||||||
{false, "+42"}, // 10
|
{true, "+42"}, // 10
|
||||||
{false, "main.css"}, // 11
|
{false, "main.css"}, // 11
|
||||||
{false, "/assets/main.css"}, // 12
|
{false, "/assets/main.css"}, // 12
|
||||||
}
|
}
|
||||||
|
@ -197,7 +208,7 @@ func TestInt32EvaluatorRaw(t *testing.T) {
|
||||||
{false, "-18446744073709553213213213213213121615"}, // 9
|
{false, "-18446744073709553213213213213213121615"}, // 9
|
||||||
{false, "42 18446744073709551615"}, // 10
|
{false, "42 18446744073709551615"}, // 10
|
||||||
{false, "--42"}, // 11
|
{false, "--42"}, // 11
|
||||||
{false, "+42"}, // 12
|
{true, "+42"}, // 12
|
||||||
{false, "main.css"}, // 13
|
{false, "main.css"}, // 13
|
||||||
{false, "/assets/main.css"}, // 14
|
{false, "/assets/main.css"}, // 14
|
||||||
}
|
}
|
||||||
|
@ -278,7 +289,7 @@ func TestUint8EvaluatorRaw(t *testing.T) {
|
||||||
{false, "+1"}, // 9
|
{false, "+1"}, // 9
|
||||||
{false, "18446744073709551615"}, // 10
|
{false, "18446744073709551615"}, // 10
|
||||||
{false, "9223372036854775807"}, // 11
|
{false, "9223372036854775807"}, // 11
|
||||||
{false, "021"}, // 12 - no leading zeroes are allowed.
|
{true, "021"}, // 12 - leading zeroes are allowed.
|
||||||
{false, "300"}, // 13
|
{false, "300"}, // 13
|
||||||
{true, "0"}, // 14
|
{true, "0"}, // 14
|
||||||
{true, "255"}, // 15
|
{true, "255"}, // 15
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package macro
|
package macro
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -47,19 +49,14 @@ var (
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
simpleNumberEval = MustRegexp("^-?[0-9]+$")
|
|
||||||
// Int or number type
|
// Int or number type
|
||||||
// both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch.
|
// both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch.
|
||||||
// If x64: -9223372036854775808 to 9223372036854775807.
|
// If x64: -9223372036854775808 to 9223372036854775807.
|
||||||
// If x32: -2147483648 to 2147483647 and etc..
|
// If x32: -2147483648 to 2147483647 and etc..
|
||||||
Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) {
|
Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !simpleNumberEval(paramValue) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := strconv.Atoi(paramValue)
|
v, err := strconv.Atoi(paramValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, true
|
return v, true
|
||||||
|
@ -89,13 +86,9 @@ var (
|
||||||
// Int8 type
|
// Int8 type
|
||||||
// -128 to 127.
|
// -128 to 127.
|
||||||
Int8 = NewMacro("int8", "", false, false, func(paramValue string) (interface{}, bool) {
|
Int8 = NewMacro("int8", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !simpleNumberEval(paramValue) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := strconv.ParseInt(paramValue, 10, 8)
|
v, err := strconv.ParseInt(paramValue, 10, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return int8(v), true
|
return int8(v), true
|
||||||
}).
|
}).
|
||||||
|
@ -118,13 +111,9 @@ var (
|
||||||
// Int16 type
|
// Int16 type
|
||||||
// -32768 to 32767.
|
// -32768 to 32767.
|
||||||
Int16 = NewMacro("int16", "", false, false, func(paramValue string) (interface{}, bool) {
|
Int16 = NewMacro("int16", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !simpleNumberEval(paramValue) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := strconv.ParseInt(paramValue, 10, 16)
|
v, err := strconv.ParseInt(paramValue, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return int16(v), true
|
return int16(v), true
|
||||||
}).
|
}).
|
||||||
|
@ -147,13 +136,9 @@ var (
|
||||||
// Int32 type
|
// Int32 type
|
||||||
// -2147483648 to 2147483647.
|
// -2147483648 to 2147483647.
|
||||||
Int32 = NewMacro("int32", "", false, false, func(paramValue string) (interface{}, bool) {
|
Int32 = NewMacro("int32", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !simpleNumberEval(paramValue) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := strconv.ParseInt(paramValue, 10, 32)
|
v, err := strconv.ParseInt(paramValue, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return int32(v), true
|
return int32(v), true
|
||||||
}).
|
}).
|
||||||
|
@ -176,13 +161,9 @@ var (
|
||||||
// Int64 as int64 type
|
// Int64 as int64 type
|
||||||
// -9223372036854775808 to 9223372036854775807.
|
// -9223372036854775808 to 9223372036854775807.
|
||||||
Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) {
|
Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !simpleNumberEval(paramValue) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := strconv.ParseInt(paramValue, 10, 64)
|
v, err := strconv.ParseInt(paramValue, 10, 64)
|
||||||
if err != nil { // if err == strconv.ErrRange...
|
if err != nil { // if err == strconv.ErrRange...
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return v, true
|
return v, true
|
||||||
}).
|
}).
|
||||||
|
@ -215,7 +196,7 @@ var (
|
||||||
Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) {
|
Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64...
|
v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64...
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return uint(v), true
|
return uint(v), true
|
||||||
}).
|
}).
|
||||||
|
@ -241,17 +222,12 @@ var (
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
|
|
||||||
// Uint8 as uint8 type
|
// Uint8 as uint8 type
|
||||||
// 0 to 255.
|
// 0 to 255.
|
||||||
Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) {
|
Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !uint8Eval(paramValue) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := strconv.ParseUint(paramValue, 10, 8)
|
v, err := strconv.ParseUint(paramValue, 10, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return uint8(v), true
|
return uint8(v), true
|
||||||
}).
|
}).
|
||||||
|
@ -282,7 +258,7 @@ var (
|
||||||
Uint16 = NewMacro("uint16", "", false, false, func(paramValue string) (interface{}, bool) {
|
Uint16 = NewMacro("uint16", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
v, err := strconv.ParseUint(paramValue, 10, 16)
|
v, err := strconv.ParseUint(paramValue, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return uint16(v), true
|
return uint16(v), true
|
||||||
}).
|
}).
|
||||||
|
@ -307,7 +283,7 @@ var (
|
||||||
Uint32 = NewMacro("uint32", "", false, false, func(paramValue string) (interface{}, bool) {
|
Uint32 = NewMacro("uint32", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
v, err := strconv.ParseUint(paramValue, 10, 32)
|
v, err := strconv.ParseUint(paramValue, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return uint32(v), true
|
return uint32(v), true
|
||||||
}).
|
}).
|
||||||
|
@ -332,7 +308,7 @@ var (
|
||||||
Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) {
|
Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
v, err := strconv.ParseUint(paramValue, 10, 64)
|
v, err := strconv.ParseUint(paramValue, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return v, true
|
return v, true
|
||||||
}).
|
}).
|
||||||
|
@ -366,22 +342,26 @@ var (
|
||||||
// in this case.
|
// in this case.
|
||||||
v, err := strconv.ParseBool(paramValue)
|
v, err := strconv.ParseBool(paramValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
return v, true
|
return v, true
|
||||||
})
|
})
|
||||||
|
|
||||||
alphabeticalEval = MustRegexp("^[a-zA-Z ]+$")
|
// ErrParamNotAlphabetical is fired when the parameter value is not an alphabetical text.
|
||||||
|
ErrParamNotAlphabetical = errors.New("parameter is not alphabetical")
|
||||||
|
alphabeticalEval = MustRegexp("^[a-zA-Z ]+$")
|
||||||
// Alphabetical letter type
|
// Alphabetical letter type
|
||||||
// letters only (upper or lowercase)
|
// letters only (upper or lowercase)
|
||||||
Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) {
|
Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !alphabeticalEval(paramValue) {
|
if !alphabeticalEval(paramValue) {
|
||||||
return nil, false
|
return fmt.Errorf("%s: %w", paramValue, ErrParamNotAlphabetical), false
|
||||||
}
|
}
|
||||||
return paramValue, true
|
return paramValue, true
|
||||||
})
|
})
|
||||||
|
|
||||||
fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$")
|
// ErrParamNotFile is fired when the parameter value is not a form of a file.
|
||||||
|
ErrParamNotFile = errors.New("parameter is not a file")
|
||||||
|
fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$")
|
||||||
// File type
|
// File type
|
||||||
// letters (upper or lowercase)
|
// letters (upper or lowercase)
|
||||||
// numbers (0-9)
|
// numbers (0-9)
|
||||||
|
@ -391,7 +371,7 @@ var (
|
||||||
// no spaces! or other character
|
// no spaces! or other character
|
||||||
File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) {
|
File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
if !fileEval(paramValue) {
|
if !fileEval(paramValue) {
|
||||||
return nil, false
|
return fmt.Errorf("%s: %w", paramValue, ErrParamNotFile), false
|
||||||
}
|
}
|
||||||
return paramValue, true
|
return paramValue, true
|
||||||
})
|
})
|
||||||
|
@ -409,7 +389,7 @@ var (
|
||||||
UUID = NewMacro("uuid", "uuidv4", false, false, func(paramValue string) (interface{}, bool) {
|
UUID = NewMacro("uuid", "uuidv4", false, false, func(paramValue string) (interface{}, bool) {
|
||||||
_, err := uuid.Parse(paramValue) // this is x10+ times faster than regexp.
|
_, err := uuid.Parse(paramValue) // this is x10+ times faster than regexp.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return paramValue, true
|
return paramValue, true
|
||||||
|
|
|
@ -30,10 +30,13 @@ type TemplateParam struct {
|
||||||
Src string `json:"src"` // the unparsed param'false source
|
Src string `json:"src"` // the unparsed param'false source
|
||||||
// Type is not useful anywhere here but maybe
|
// Type is not useful anywhere here but maybe
|
||||||
// it's useful on host to decide how to convert the path template to specific router's syntax
|
// it's useful on host to decide how to convert the path template to specific router's syntax
|
||||||
Type ast.ParamType `json:"type"`
|
Type ast.ParamType `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
ErrCode int `json:"errCode"`
|
ErrCode int `json:"errCode"`
|
||||||
|
// Note that, the value MUST BE a type of `func(iris.Context, err error)`.
|
||||||
|
HandleError interface{} `json:"-"` /* It's not an typed value because of import-cycle,
|
||||||
|
// neither a special struct required, see `handler.MakeFilter`. */
|
||||||
TypeEvaluator ParamEvaluator `json:"-"`
|
TypeEvaluator ParamEvaluator `json:"-"`
|
||||||
Funcs []reflect.Value `json:"-"`
|
Funcs []reflect.Value `json:"-"`
|
||||||
|
|
||||||
|
@ -53,7 +56,7 @@ func (p TemplateParam) preComputed() TemplateParam {
|
||||||
// i.e {myparam} or {myparam:string} or {myparam:path} ->
|
// i.e {myparam} or {myparam:string} or {myparam:path} ->
|
||||||
// their type evaluator is nil because they don't do any checks and they don't change
|
// their type evaluator is nil because they don't do any checks and they don't change
|
||||||
// the default parameter value's type (string) so no need for any work).
|
// the default parameter value's type (string) so no need for any work).
|
||||||
p.canEval = p.TypeEvaluator != nil || len(p.Funcs) > 0 || p.ErrCode != parser.DefaultParamErrorCode
|
p.canEval = p.TypeEvaluator != nil || len(p.Funcs) > 0 || p.ErrCode != parser.DefaultParamErrorCode || p.HandleError != nil
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
@ -64,6 +67,10 @@ func (p *TemplateParam) CanEval() bool {
|
||||||
return p.canEval
|
return p.canEval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type errorInterface interface {
|
||||||
|
Error() string
|
||||||
|
}
|
||||||
|
|
||||||
// Eval is the most critical part of the TemplateParam.
|
// Eval is the most critical part of the TemplateParam.
|
||||||
// It is responsible to return the type-based value if passed otherwise nil.
|
// It is responsible to return the type-based value if passed otherwise nil.
|
||||||
// If the "paramValue" is the correct type of the registered parameter type
|
// If the "paramValue" is the correct type of the registered parameter type
|
||||||
|
@ -71,21 +78,27 @@ func (p *TemplateParam) CanEval() bool {
|
||||||
//
|
//
|
||||||
// It is called from the converted macro handler (middleware)
|
// It is called from the converted macro handler (middleware)
|
||||||
// from the higher-level component of "kataras/iris/macro/handler#MakeHandler".
|
// from the higher-level component of "kataras/iris/macro/handler#MakeHandler".
|
||||||
func (p *TemplateParam) Eval(paramValue string) interface{} {
|
func (p *TemplateParam) Eval(paramValue string) (interface{}, bool) {
|
||||||
if p.TypeEvaluator == nil {
|
if p.TypeEvaluator == nil {
|
||||||
for _, fn := range p.stringInFuncs {
|
for _, fn := range p.stringInFuncs {
|
||||||
if !fn(paramValue) {
|
if !fn(paramValue) {
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return paramValue
|
return paramValue, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmt.Printf("macro/template.go#L88: Eval for param value: %s and p.Src: %s\n", paramValue, p.Src)
|
// fmt.Printf("macro/template.go#L88: Eval for param value: %s and p.Src: %s\n", paramValue, p.Src)
|
||||||
|
|
||||||
newValue, passed := p.TypeEvaluator(paramValue)
|
newValue, passed := p.TypeEvaluator(paramValue)
|
||||||
if !passed {
|
if !passed {
|
||||||
return nil
|
if newValue != nil && p.HandleError != nil { // return this error only when a HandleError was registered.
|
||||||
|
if _, ok := newValue.(errorInterface); ok {
|
||||||
|
return newValue, false // this is an error, see `HandleError` and `MakeFilter`.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.Funcs) > 0 {
|
if len(p.Funcs) > 0 {
|
||||||
|
@ -94,14 +107,14 @@ func (p *TemplateParam) Eval(paramValue string) interface{} {
|
||||||
// or make it as func(interface{}) bool and pass directly the "newValue"
|
// or make it as func(interface{}) bool and pass directly the "newValue"
|
||||||
// but that would not be as easy for end-developer, so keep that "slower":
|
// but that would not be as easy for end-developer, so keep that "slower":
|
||||||
if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool
|
if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmt.Printf("macro/template.go: passed with value: %v and type: %T\n", newValue, newValue)
|
// fmt.Printf("macro/template.go: passed with value: %v and type: %T\n", newValue, newValue)
|
||||||
|
|
||||||
return newValue
|
return newValue, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions)
|
// Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions)
|
||||||
|
@ -130,6 +143,7 @@ func Parse(src string, macros Macros) (Template, error) {
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Index: idx,
|
Index: idx,
|
||||||
ErrCode: p.ErrorCode,
|
ErrCode: p.ErrorCode,
|
||||||
|
HandleError: m.handleError,
|
||||||
TypeEvaluator: typEval,
|
TypeEvaluator: typEval,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user