mirror of
https://github.com/kataras/iris.git
synced 2025-03-13 21:36:28 +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.
|
||||
|
||||
- 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:
|
||||
|
||||
|
|
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