Build a better web, together!

Former-commit-id: 3cfe87da405d0ff749e1a1010660c556b047f333
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-03-19 03:49:17 +02:00
parent 0733ea37c0
commit 55e1e79816
7 changed files with 514 additions and 1 deletions

View File

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

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

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