mirror of
https://github.com/kataras/iris.git
synced 2025-03-15 17:36:29 +01:00
Build a better web, together!
Former-commit-id: 3cfe87da405d0ff749e1a1010660c556b047f333
This commit is contained in:
parent
0733ea37c0
commit
55e1e79816
16
HISTORY.md
16
HISTORY.md
|
@ -47,7 +47,21 @@ to adapt the new changes to your application, it contains an overview of the new
|
||||||
|
|
||||||
- Add `.Regex` middleware which does path validation using the `regexp` package, i.e `.Regex("param", "[0-9]+$")`. Useful for routers that don't support regex route path validation out-of-the-box.
|
- Add `.Regex` middleware which does path validation using the `regexp` package, i.e `.Regex("param", "[0-9]+$")`. Useful for routers that don't support regex route path validation out-of-the-box.
|
||||||
|
|
||||||
- Websocket additions: `c.Context() *iris.Context`, `ws.GetConnectionsByRoom("room name") []websocket.Connection`, `c.OnLeave(func(roomName string){})`, `c.Values().Set(key,value)/.Get(key).Reset()` (where ws:websocket.Server insance, where c:websocket.Connection instance)
|
- Websocket additions: `c.Context() *iris.Context`, `ws.GetConnectionsByRoom("room name") []websocket.Connection`, `c.OnLeave(func(roomName string){})`,
|
||||||
|
```go
|
||||||
|
// SetValue sets a key-value pair on the connection's mem store.
|
||||||
|
c.SetValue(key string, value interface{})
|
||||||
|
// GetValue gets a value by its key from the connection's mem store.
|
||||||
|
c.GetValue(key string) interface{}
|
||||||
|
// GetValueArrString gets a value as []string by its key from the connection's mem store.
|
||||||
|
c.GetValueArrString(key string) []string
|
||||||
|
// GetValueString gets a value as string by its key from the connection's mem store.
|
||||||
|
c.GetValueString(key string) string
|
||||||
|
// GetValueInt gets a value as integer by its key from the connection's mem store.
|
||||||
|
c.GetValueInt(key string) int
|
||||||
|
|
||||||
|
```
|
||||||
|
[examples here](https://github.com/kataras/iris/blob/v6/adaptors/websocket/_examples).
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
|
|
38
_future/README.md
Normal file
38
_future/README.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<p align="center">
|
||||||
|
<a href="http://iris-go.com/">
|
||||||
|
<img width="570" src="https://github.com/iris-contrib/website/raw/gh-pages/assets/simplicity_random_image_but_suitable_because_iris_has_the_same_slogan.gif" alt="Simplicity Equals Productivity" />
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="http://iris-go.com/">Simplicity Equals Productivity</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Future | Inspiration for new developers.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This folder contains features that are marked as 'work-in-progress', they can take a long to be fully implemented and adopted to the Iris framework itself, even months.
|
||||||
|
|
||||||
|
Some people may find that bad idea, for many and different reasons, to upload them on that public repository so soon.
|
||||||
|
|
||||||
|
But I think that it is a good place for new developers to view and track how a feature is being implemented step-by-step. How I develop Iris step-by-step.
|
||||||
|
|
||||||
|
|
||||||
|
I have collected some tips for you!
|
||||||
|
|
||||||
|
- Do What They Think You Can't Do.
|
||||||
|
- It's Not How Good You Are, It's How Good You Want To Be.
|
||||||
|
- Genius is 1% Inspiration, 99% Perspiration.
|
||||||
|
- You Are Your Only Limit.
|
||||||
|
- Do Something Today That Your Future Self Will Thank You.
|
||||||
|
- Don't Call It A Dream - Call It A Plan.
|
||||||
|
|
||||||
|
|
||||||
|
And never forget, **If I can do it, so can you!**
|
||||||
|
|
||||||
|
|
22
_future/macros.go
Normal file
22
_future/macros.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
Here I should think a way to link the framework and user-defined macros
|
||||||
|
with their one-by-one(?) custom function(s) and all these with one or more PathTmpls or visa-versa
|
||||||
|
|
||||||
|
These should be linked at .Boot time, so before the server starts.
|
||||||
|
Tthe work I have done so far it should be resulted in a single middleware
|
||||||
|
which will be prepended to the zero position, so no performance cost when no new features are used.
|
||||||
|
The performance should be the same as now if the path doesn't contains
|
||||||
|
any macros:
|
||||||
|
macro = /api/users/{id:int} or /api/users/{id:int range(1,100) !404}
|
||||||
|
no macro = /api/users/id}).
|
||||||
|
|
||||||
|
I should add a good detailed examples on how the user can override or add his/her
|
||||||
|
own macros and optional functions can be followed (i.e, func = range(1,5)).
|
||||||
|
|
||||||
|
Of course no breaking-changes to the user's workflow(I should not and not need to touch the existing router adaptors).
|
||||||
|
|
||||||
|
*/
|
163
_future/param_parser.go
Normal file
163
_future/param_parser.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParamTmpl struct {
|
||||||
|
// id
|
||||||
|
Name string
|
||||||
|
// int range(1,5)!fail # fail fails on int parse or on range, it will work reclusive
|
||||||
|
Expression string
|
||||||
|
// fail
|
||||||
|
FailStatusCode int
|
||||||
|
|
||||||
|
Macro MacroTmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
type MacroTmpl struct {
|
||||||
|
// int
|
||||||
|
Name string
|
||||||
|
// Macro will allow more than one funcs.
|
||||||
|
// []*MacroFuncs{ {Name: range, Params: []string{1,5}}}
|
||||||
|
Funcs []MacroFuncTmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
type MacroFuncTmpl struct {
|
||||||
|
// range
|
||||||
|
Name string
|
||||||
|
// 1,5
|
||||||
|
Params []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ParamNameSeperator = ':'
|
||||||
|
FuncSeperator = ' '
|
||||||
|
FuncStart = '('
|
||||||
|
FuncEnd = ')'
|
||||||
|
FuncParamSeperator = ','
|
||||||
|
FailSeparator = '!'
|
||||||
|
ORSymbol = '|'
|
||||||
|
ANDSymbol = '&'
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultFailStatusCode = 0
|
||||||
|
|
||||||
|
// Parse i.e:
|
||||||
|
// id:int range(1,5) otherFunc(3) !404
|
||||||
|
//
|
||||||
|
// id = param name | can front-end end here but the parser should add :any
|
||||||
|
// int = marco | can end here
|
||||||
|
//
|
||||||
|
// range = marco's funcs(range)
|
||||||
|
// 1,5 = range's func.params | can end here
|
||||||
|
// +
|
||||||
|
// otherFunc = marco's funcs(otherFunc)
|
||||||
|
// 3 = otherFunc's func.params | can end here
|
||||||
|
//
|
||||||
|
// 404 = fail http custom error status code -> handler , will fail rescuslive
|
||||||
|
func ParseParam(source string) (*ParamTmpl, error) {
|
||||||
|
// first, do a simple check if that's 'valid'
|
||||||
|
sepIndex := strings.IndexRune(source, ParamNameSeperator)
|
||||||
|
|
||||||
|
if sepIndex <= 0 {
|
||||||
|
// if not found or
|
||||||
|
// if starts with :
|
||||||
|
return nil, fmt.Errorf("invalid source '%s', separator should be after the parameter name", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := new(ParamTmpl)
|
||||||
|
// id:int range(1,5)
|
||||||
|
// id:int min(1) max(5)
|
||||||
|
// id:int range(1,5)!404 or !404, space doesn't matters on fail error code.
|
||||||
|
cursor := 0
|
||||||
|
for i := 0; i < len(source); i++ {
|
||||||
|
if source[i] == ParamNameSeperator {
|
||||||
|
if i+1 >= len(source) {
|
||||||
|
return nil, fmt.Errorf("missing marco or raw expression after seperator, on source '%s'", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// id: , take the left, skip the : and continue
|
||||||
|
t.Name = source[0:i]
|
||||||
|
// set the expression, after the i, i.e:
|
||||||
|
// int range(1,5)
|
||||||
|
t.Expression = source[i+1:]
|
||||||
|
// set the macro's name to the full expression
|
||||||
|
// because we don't know if the user has put functions
|
||||||
|
// and we follow the < left 'pattern'
|
||||||
|
// (I don't know if that's valid but that is what
|
||||||
|
// I think to do and is working).
|
||||||
|
t.Macro = MacroTmpl{Name: t.Expression}
|
||||||
|
|
||||||
|
// cursor knows the last known(parsed) char position.
|
||||||
|
cursor = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// int ...
|
||||||
|
if source[i] == FuncSeperator {
|
||||||
|
// take the left part: int if it's the first
|
||||||
|
// space after the param name
|
||||||
|
if t.Macro.Name == t.Expression {
|
||||||
|
t.Macro.Name = source[cursor:i]
|
||||||
|
} // else we have one or more functions, skip.
|
||||||
|
|
||||||
|
cursor = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if source[i] == FuncStart {
|
||||||
|
// take the left part: range
|
||||||
|
funcName := source[cursor:i]
|
||||||
|
t.Macro.Funcs = append(t.Macro.Funcs, MacroFuncTmpl{Name: funcName})
|
||||||
|
|
||||||
|
cursor = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 1,5)
|
||||||
|
if source[i] == FuncEnd {
|
||||||
|
// check if we have end parenthesis but not start
|
||||||
|
if len(t.Macro.Funcs) == 0 {
|
||||||
|
return nil, fmt.Errorf("missing start macro's '%s' function, on source '%s'", t.Macro.Name, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// take the left part, between Start and End: 1,5
|
||||||
|
funcParamsStr := source[cursor:i]
|
||||||
|
|
||||||
|
funcParams := strings.SplitN(funcParamsStr, string(FuncParamSeperator), -1)
|
||||||
|
t.Macro.Funcs[len(t.Macro.Funcs)-1].Params = funcParams
|
||||||
|
|
||||||
|
cursor = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if source[i] == FailSeparator {
|
||||||
|
// it should be the last element
|
||||||
|
// so no problem if we set the cursor here and work with that
|
||||||
|
// we will not need that later.
|
||||||
|
cursor = i + 1
|
||||||
|
|
||||||
|
if cursor >= len(source) {
|
||||||
|
return nil, fmt.Errorf("missing fail status code after '%q', on source '%s'", FailSeparator, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
failCodeStr := source[cursor:] // should be the last
|
||||||
|
failCode, err := strconv.Atoi(failCodeStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail status code should be integer but got '%s', on source '%s'", failCodeStr, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.FailStatusCode = failCode
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.FailStatusCode == 0 {
|
||||||
|
t.FailStatusCode = DefaultFailStatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
115
_future/param_parser_test.go
Normal file
115
_future/param_parser_test.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testParamParser(source string, t *ParamTmpl) error {
|
||||||
|
result, err := ParseParam(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check of param name
|
||||||
|
if expected, got := t.Name, result.Name; expected != got {
|
||||||
|
return fmt.Errorf("Expecting Name to be '%s' but got '%s'", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check on macro name
|
||||||
|
if expected, got := t.Macro.Name, result.Macro.Name; expected != got {
|
||||||
|
return fmt.Errorf("Expecting Macro.Name to be '%s' but got '%s'", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check of length of the macro's funcs
|
||||||
|
if expected, got := len(t.Macro.Funcs), len(result.Macro.Funcs); expected != got {
|
||||||
|
return fmt.Errorf("Expecting Macro.Funs Len to be '%d' but got '%d'", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check of the functions
|
||||||
|
if len(t.Macro.Funcs) > 0 {
|
||||||
|
if expected, got := t.Macro.Funcs[0].Name, result.Macro.Funcs[0].Name; expected != got {
|
||||||
|
return fmt.Errorf("Expecting Macro.Funcs[0].Name to be '%s' but got '%s'", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, got := t.Macro.Funcs[0].Params, result.Macro.Funcs[0].Params; expected[0] != got[0] {
|
||||||
|
return fmt.Errorf("Expecting Macro.Funcs[0].Params to be '%s' but got '%s'", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// and the final test for all, to be sure
|
||||||
|
// here the details are more
|
||||||
|
if !reflect.DeepEqual(*t, *result) {
|
||||||
|
return fmt.Errorf("Expected and Result don't match. Details:\n%#v\n%#v", *t, *result)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamParser(t *testing.T) {
|
||||||
|
|
||||||
|
// id:int
|
||||||
|
expected := &ParamTmpl{
|
||||||
|
Name: "id",
|
||||||
|
Expression: "int",
|
||||||
|
Macro: MacroTmpl{Name: "int"},
|
||||||
|
}
|
||||||
|
source := expected.Name + string(ParamNameSeperator) + expected.Expression
|
||||||
|
if err := testParamParser(expected.Name+":"+expected.Expression, expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// id:int range(1,5)
|
||||||
|
expected = &ParamTmpl{
|
||||||
|
Name: "id",
|
||||||
|
Expression: "int range(1,5)",
|
||||||
|
Macro: MacroTmpl{Name: "int",
|
||||||
|
Funcs: []MacroFuncTmpl{
|
||||||
|
MacroFuncTmpl{Name: "range", Params: []string{"1", "5"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
source = expected.Name + string(ParamNameSeperator) + expected.Expression
|
||||||
|
if err := testParamParser(expected.Name+":"+expected.Expression, expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// id:int min(1) max(5)
|
||||||
|
expected = &ParamTmpl{
|
||||||
|
Name: "id",
|
||||||
|
Expression: "int min(1) max(5)",
|
||||||
|
Macro: MacroTmpl{Name: "int",
|
||||||
|
Funcs: []MacroFuncTmpl{
|
||||||
|
MacroFuncTmpl{Name: "min", Params: []string{"1"}},
|
||||||
|
MacroFuncTmpl{Name: "max", Params: []string{"5"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
source = expected.Name + string(ParamNameSeperator) + expected.Expression
|
||||||
|
if err := testParamParser(expected.Name+":"+expected.Expression, expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// username:string contains('blabla') max(20) !402
|
||||||
|
expected = &ParamTmpl{
|
||||||
|
Name: "username",
|
||||||
|
Expression: "string contains(blabla) max(20) !402",
|
||||||
|
FailStatusCode: 402,
|
||||||
|
Macro: MacroTmpl{Name: "string",
|
||||||
|
Funcs: []MacroFuncTmpl{
|
||||||
|
MacroFuncTmpl{Name: "contains", Params: []string{"blabla"}},
|
||||||
|
MacroFuncTmpl{Name: "max", Params: []string{"20"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
source = expected.Name + string(ParamNameSeperator) + expected.Expression
|
||||||
|
if err := testParamParser(source, expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
73
_future/path_parser.go
Normal file
73
_future/path_parser.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PathTmpl struct {
|
||||||
|
Params []PathParamTmpl
|
||||||
|
SegmentsLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathParamTmpl struct {
|
||||||
|
SegmentIndex int
|
||||||
|
Param ParamTmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PathSeparator = '/'
|
||||||
|
// Out means that it doesn't being included in param.
|
||||||
|
ParamStartOut = '{'
|
||||||
|
ParamEndOut = '}'
|
||||||
|
)
|
||||||
|
|
||||||
|
// /users/{id:int range(1,5)}/profile
|
||||||
|
// parses only the contents inside {}
|
||||||
|
// but it gives back the position so it will be '1'
|
||||||
|
func ParsePath(source string) (*PathTmpl, error) {
|
||||||
|
t := new(PathTmpl)
|
||||||
|
cursor := 0
|
||||||
|
segmentIndex := -1
|
||||||
|
|
||||||
|
// error if path is empty
|
||||||
|
if len(source) < 1 {
|
||||||
|
return nil, fmt.Errorf("source cannot be empty ")
|
||||||
|
}
|
||||||
|
// error if not starts with '/'
|
||||||
|
if source[0] != PathSeparator {
|
||||||
|
return nil, fmt.Errorf("source '%s' should start with a path separator(%q)", source, PathSeparator)
|
||||||
|
}
|
||||||
|
// if path ends with '/' remove the last '/'
|
||||||
|
if source[len(source)-1] == PathSeparator {
|
||||||
|
source = source[0 : len(source)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range source {
|
||||||
|
if source[i] == PathSeparator {
|
||||||
|
segmentIndex++
|
||||||
|
t.SegmentsLength++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if source[i] == ParamStartOut {
|
||||||
|
cursor = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if source[i] == ParamEndOut {
|
||||||
|
// take the left part id:int range(1,5)
|
||||||
|
paramSource := source[cursor:i]
|
||||||
|
paramTmpl, err := ParseParam(paramSource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Params = append(t.Params, PathParamTmpl{SegmentIndex: segmentIndex, Param: *paramTmpl})
|
||||||
|
|
||||||
|
cursor = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
88
_future/path_parser_test.go
Normal file
88
_future/path_parser_test.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testPathParser(source string, t *PathTmpl) error {
|
||||||
|
result, err := ParsePath(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, got := t.SegmentsLength, result.SegmentsLength; expected != got {
|
||||||
|
return fmt.Errorf("expecting SegmentsLength to be %d but got %d", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, got := t.Params, result.Params; len(expected) != len(got) {
|
||||||
|
return fmt.Errorf("expecting Params length to be %d but got %d", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(*t, *result) {
|
||||||
|
return fmt.Errorf("Expected and Result don't match. Details:\n%#v\nvs\n%#v\n", *t, *result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathParser(t *testing.T) {
|
||||||
|
// /users/{id:int}
|
||||||
|
expected := &PathTmpl{
|
||||||
|
SegmentsLength: 2,
|
||||||
|
Params: []PathParamTmpl{
|
||||||
|
PathParamTmpl{
|
||||||
|
SegmentIndex: 1,
|
||||||
|
Param: ParamTmpl{
|
||||||
|
Name: "id",
|
||||||
|
Expression: "int",
|
||||||
|
Macro: MacroTmpl{Name: "int"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testPathParser("/users/{id:int}", expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// /api/users/{id:int range(1,5) !404}/other/{username:string contains(s) min(10) !402}
|
||||||
|
expected = &PathTmpl{
|
||||||
|
SegmentsLength: 5,
|
||||||
|
Params: []PathParamTmpl{
|
||||||
|
PathParamTmpl{
|
||||||
|
SegmentIndex: 2,
|
||||||
|
Param: ParamTmpl{
|
||||||
|
Name: "id",
|
||||||
|
Expression: "int range(1,5) !404",
|
||||||
|
FailStatusCode: 404,
|
||||||
|
Macro: MacroTmpl{Name: "int",
|
||||||
|
Funcs: []MacroFuncTmpl{
|
||||||
|
MacroFuncTmpl{Name: "range", Params: []string{"1", "5"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PathParamTmpl{
|
||||||
|
SegmentIndex: 4,
|
||||||
|
Param: ParamTmpl{
|
||||||
|
Name: "username",
|
||||||
|
Expression: "string contains(s) min(10) !402",
|
||||||
|
FailStatusCode: 402,
|
||||||
|
Macro: MacroTmpl{Name: "string",
|
||||||
|
Funcs: []MacroFuncTmpl{
|
||||||
|
MacroFuncTmpl{Name: "contains", Params: []string{"s"}},
|
||||||
|
MacroFuncTmpl{Name: "min", Params: []string{"10"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := testPathParser("/api/users/{id:int range(1,5) !404}/other/{username:string contains(s) min(10) !402}", expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user