diff --git a/HISTORY.md b/HISTORY.md
index c5bdcf37..74af6f46 100644
--- a/HISTORY.md
+++ b/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:
diff --git a/_future/README.md b/_future/README.md
new file mode 100644
index 00000000..7083985f
--- /dev/null
+++ b/_future/README.md
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+Simplicity Equals Productivity
+
+
+
+
+
+
+# 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!**
+
+
diff --git a/_future/macros.go b/_future/macros.go
new file mode 100644
index 00000000..48a5be96
--- /dev/null
+++ b/_future/macros.go
@@ -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).
+
+*/
diff --git a/_future/param_parser.go b/_future/param_parser.go
new file mode 100644
index 00000000..49ed72c8
--- /dev/null
+++ b/_future/param_parser.go
@@ -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
+}
diff --git a/_future/param_parser_test.go b/_future/param_parser_test.go
new file mode 100644
index 00000000..486e0820
--- /dev/null
+++ b/_future/param_parser_test.go
@@ -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
+ }
+
+}
diff --git a/_future/path_parser.go b/_future/path_parser.go
new file mode 100644
index 00000000..5c6a54ff
--- /dev/null
+++ b/_future/path_parser.go
@@ -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
+}
diff --git a/_future/path_parser_test.go b/_future/path_parser_test.go
new file mode 100644
index 00000000..39c160f7
--- /dev/null
+++ b/_future/path_parser_test.go
@@ -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
+ }
+}