mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +01:00
_future
Former-commit-id: 06ad874613c270383be5a7355cbb23a2dcf7a903
This commit is contained in:
parent
7e94befac8
commit
64ecc88195
|
@ -21,3 +21,20 @@ Of course no breaking-changes to the user's workflow.
|
||||||
I should not and not need to touch the existing router adaptors.
|
I should not and not need to touch the existing router adaptors.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* 23 March 2017
|
||||||
|
Essentially almost finish, on "dirty" code:
|
||||||
|
- we can define custom macros, custom validators per custom macro or to all of the available macros,
|
||||||
|
- parse the macro and macro funcs to iris.HandlerFunc and passed as middleware
|
||||||
|
- use reflection to add custom function signature without need to convert and check for arguments length
|
||||||
|
(same performance as with the 'hard-manual' way because the func which does all these checks is executed on boot time)
|
||||||
|
|
||||||
|
Todo:
|
||||||
|
- Make this new syntax compatible with the already router adaptors (can be done easy)
|
||||||
|
- We need to clean the code and think a way to adapt that to Iris in order to be
|
||||||
|
easy-to-use while in the same time provide the necessary api for advanced use cases.
|
||||||
|
|
||||||
|
|
||||||
|
I should also think if this feature worths the time to be extended into
|
||||||
|
a complete interpreter(with tokens, lexers, parsers and AST) and name it 'iel/Iris Expression Language' which could be used everywhere in the framework.
|
||||||
|
*/
|
||||||
|
|
|
@ -47,6 +47,8 @@ func link(path string, mac _macros) iris.HandlerFunc {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// println(tmpl.Params[0].Param.FailStatusCode)
|
||||||
// link the path, based on its template with a macro
|
// link the path, based on its template with a macro
|
||||||
// and return a new compiled macro or a list of iris handlers
|
// and return a new compiled macro or a list of iris handlers
|
||||||
// in order to be prepended on the original route or make a different function for that?
|
// in order to be prepended on the original route or make a different function for that?
|
||||||
|
@ -334,8 +336,8 @@ func TestMacros(t *testing.T) {
|
||||||
// when string macro is used:
|
// when string macro is used:
|
||||||
addMacroFunc("string", "contains", macroFuncFrom(func(text string) _macrofn {
|
addMacroFunc("string", "contains", macroFuncFrom(func(text string) _macrofn {
|
||||||
return func(paramValue string) bool {
|
return func(paramValue string) bool {
|
||||||
println("from string:contains instead of any:string")
|
// println("from string:contains instead of any:string")
|
||||||
println("'" + text + "' vs '" + paramValue + "'")
|
// println("'" + text + "' vs '" + paramValue + "'")
|
||||||
|
|
||||||
return strings.Contains(paramValue, text)
|
return strings.Contains(paramValue, text)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,44 @@ const (
|
||||||
FuncEnd = ')'
|
FuncEnd = ')'
|
||||||
FuncParamSeperator = ','
|
FuncParamSeperator = ','
|
||||||
FailSeparator = '!'
|
FailSeparator = '!'
|
||||||
ORSymbol = '|'
|
|
||||||
ANDSymbol = '&'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultFailStatusCode = 404
|
const DefaultFailStatusCode = 404
|
||||||
|
|
||||||
|
type CursorState int
|
||||||
|
|
||||||
|
func (cs *CursorState) Is(s CursorState) bool {
|
||||||
|
c := *cs
|
||||||
|
if c == s {
|
||||||
|
switch s {
|
||||||
|
case CursorStateNone:
|
||||||
|
*cs = CursorStateStarted
|
||||||
|
case CursorStateStarted:
|
||||||
|
*cs = CursorStatePending
|
||||||
|
case CursorStatePending:
|
||||||
|
*cs = CursorStateRecording
|
||||||
|
case CursorStateRecording:
|
||||||
|
*cs = CursorStateStarted
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// we use that to set a value of each "state".
|
||||||
|
// because macro's function's arguments can accepts space and regex with any character
|
||||||
|
const (
|
||||||
|
// no : is found yet
|
||||||
|
CursorStateNone CursorState = iota
|
||||||
|
// macro name and first expression guess parsed
|
||||||
|
// and we' ready to walk forward
|
||||||
|
CursorStateStarted
|
||||||
|
// after space, waiting for a macro func begin or !+fail_status_code
|
||||||
|
CursorStatePending
|
||||||
|
// inside the macro func, after the opening parenthesis
|
||||||
|
CursorStateRecording
|
||||||
|
)
|
||||||
|
|
||||||
// Parse i.e:
|
// Parse i.e:
|
||||||
// id:int range(1,5) otherFunc(3) !404
|
// id:int range(1,5) otherFunc(3) !404
|
||||||
//
|
//
|
||||||
|
@ -72,18 +104,15 @@ func ParseParam(source string) (*ParamTmpl, error) {
|
||||||
// id:int range(1,5)
|
// id:int range(1,5)
|
||||||
// id:int min(1) max(5)
|
// id:int min(1) max(5)
|
||||||
// id:int range(1,5)!404 or !404, space doesn't matters on fail error code.
|
// id:int range(1,5)!404 or !404, space doesn't matters on fail error code.
|
||||||
|
// cursor := 0
|
||||||
|
state := CursorStateNone
|
||||||
cursor := 0
|
cursor := 0
|
||||||
// waitForFunc setted to true when we validate that we have macro's functions
|
|
||||||
// so we can check for parenthesis.
|
|
||||||
// We need that check because the user may add a regexp with parenthesis.
|
|
||||||
// Although this will not be recommended, user is better to create a macro for its regexp
|
|
||||||
// in order to use it everywhere and reduce code duplication.
|
|
||||||
waitForFunc := false
|
|
||||||
// when inside macro func we don't need to check for anything else, because it could
|
|
||||||
// break the tmpl, i.e FuncSeperator (space) if "contains( )".
|
|
||||||
insideFunc := false
|
|
||||||
for i := 0; i < len(source); i++ {
|
for i := 0; i < len(source); i++ {
|
||||||
if source[i] == ParamNameSeperator {
|
// TODO: find a better way instead of introducing variables like waitForFunc, insideFunc,
|
||||||
|
// one way is to move the functions with the reverse order but this can fix the problem for now
|
||||||
|
// later it will introduce new bugs, we can find a better static way to check these things, tomorrow.
|
||||||
|
// :int ...
|
||||||
|
if source[i] == ParamNameSeperator && state.Is(CursorStateNone) {
|
||||||
if i+1 >= len(source) {
|
if i+1 >= len(source) {
|
||||||
return nil, fmt.Errorf("missing marco or raw expression after seperator, on source '%s'", source)
|
return nil, fmt.Errorf("missing marco or raw expression after seperator, on source '%s'", source)
|
||||||
}
|
}
|
||||||
|
@ -100,40 +129,34 @@ func ParseParam(source string) (*ParamTmpl, error) {
|
||||||
// I think to do and is working).
|
// I think to do and is working).
|
||||||
t.Macro = MacroTmpl{Name: t.Expression}
|
t.Macro = MacroTmpl{Name: t.Expression}
|
||||||
|
|
||||||
// cursor knows the last known(parsed) char position.
|
|
||||||
cursor = i + 1
|
cursor = i + 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// TODO: find a better way instead of introducing variables like waitForFunc, insideFunc,
|
|
||||||
// one way is to move the functions with the reverse order but this can fix the problem for now
|
|
||||||
// later it will introduce new bugs, we can find a better static way to check these things, tomorrow.
|
|
||||||
|
|
||||||
// int ...
|
if source[i] == FuncSeperator && state.Is(CursorStateStarted) {
|
||||||
if !waitForFunc && source[i] == FuncSeperator {
|
|
||||||
// take the left part: int if it's the first
|
// take the left part: int if it's the first
|
||||||
// space after the param name
|
// space after the param name
|
||||||
if t.Macro.Name == t.Expression {
|
if t.Macro.Name == t.Expression {
|
||||||
t.Macro.Name = source[cursor:i]
|
t.Macro.Name = source[cursor:i]
|
||||||
} // else we have one or more functions, skip.
|
} // else we have one or more functions, skip.
|
||||||
waitForFunc = true
|
|
||||||
cursor = i + 1
|
cursor = i + 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not inside a func body
|
// if not inside a func body
|
||||||
// the cursor is a point which can receive a func
|
// the cursor is a point which can receive a func
|
||||||
// starts with (
|
// starts with (
|
||||||
if !insideFunc && waitForFunc && source[i] == FuncStart {
|
if source[i] == FuncStart && state.Is(CursorStatePending) {
|
||||||
insideFunc = true
|
|
||||||
// take the left part: range
|
// take the left part: range
|
||||||
funcName := source[cursor:i]
|
funcName := source[cursor:i]
|
||||||
t.Macro.Funcs = append(t.Macro.Funcs, MacroFuncTmpl{Name: funcName})
|
t.Macro.Funcs = append(t.Macro.Funcs, MacroFuncTmpl{Name: funcName})
|
||||||
|
|
||||||
cursor = i + 1
|
cursor = i + 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1,5)
|
// 1,5)
|
||||||
// we are inside func and )
|
// we are inside func and )
|
||||||
if insideFunc && source[i] == FuncEnd {
|
if source[i] == FuncEnd && state.Is(CursorStateRecording) {
|
||||||
// check if we have end parenthesis but not start
|
// check if we have end parenthesis but not start
|
||||||
if len(t.Macro.Funcs) == 0 {
|
if len(t.Macro.Funcs) == 0 {
|
||||||
return nil, fmt.Errorf("missing start macro's '%s' function, on source '%s'", t.Macro.Name, source)
|
return nil, fmt.Errorf("missing start macro's '%s' function, on source '%s'", t.Macro.Name, source)
|
||||||
|
@ -141,25 +164,19 @@ func ParseParam(source string) (*ParamTmpl, error) {
|
||||||
|
|
||||||
// take the left part, between Start and End: 1,5
|
// take the left part, between Start and End: 1,5
|
||||||
funcParamsStr := source[cursor:i]
|
funcParamsStr := source[cursor:i]
|
||||||
println("param_parser.go:41: '" + funcParamsStr + "'")
|
|
||||||
|
|
||||||
funcParams := strings.SplitN(funcParamsStr, string(FuncParamSeperator), -1)
|
funcParams := strings.SplitN(funcParamsStr, string(FuncParamSeperator), -1)
|
||||||
t.Macro.Funcs[len(t.Macro.Funcs)-1].Params = funcParams
|
t.Macro.Funcs[len(t.Macro.Funcs)-1].Params = funcParams
|
||||||
|
|
||||||
cursor = i + 1
|
cursor = i + 1
|
||||||
|
|
||||||
insideFunc = false // ignore ')' until new '('
|
|
||||||
waitForFunc = false // wait for the next space to not ignore '('
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if source[i] == FailSeparator {
|
if source[i] == FailSeparator && (state.Is(CursorStateStarted) || state.Is(CursorStatePending)) {
|
||||||
// it should be the last element
|
// it should be the last element
|
||||||
// so no problem if we set the cursor here and work with that
|
// so no problem if we set the cursor here and work with that
|
||||||
// we will not need that later.
|
// we will not need that later.
|
||||||
cursor = i + 1
|
cursor += 1
|
||||||
|
if i+1 >= len(source) {
|
||||||
if cursor >= len(source) {
|
|
||||||
return nil, fmt.Errorf("missing fail status code after '%q', on source '%s'", FailSeparator, source)
|
return nil, fmt.Errorf("missing fail status code after '%q', on source '%s'", FailSeparator, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +188,7 @@ func ParseParam(source string) (*ParamTmpl, error) {
|
||||||
|
|
||||||
t.FailStatusCode = failCode
|
t.FailStatusCode = failCode
|
||||||
|
|
||||||
continue
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,9 +51,10 @@ func TestParamParser(t *testing.T) {
|
||||||
|
|
||||||
// id:int
|
// id:int
|
||||||
expected := &ParamTmpl{
|
expected := &ParamTmpl{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
Expression: "int",
|
Expression: "int",
|
||||||
Macro: MacroTmpl{Name: "int"},
|
FailStatusCode: 404,
|
||||||
|
Macro: MacroTmpl{Name: "int"},
|
||||||
}
|
}
|
||||||
source := expected.Name + string(ParamNameSeperator) + expected.Expression
|
source := expected.Name + string(ParamNameSeperator) + expected.Expression
|
||||||
if err := testParamParser(expected.Name+":"+expected.Expression, expected); err != nil {
|
if err := testParamParser(expected.Name+":"+expected.Expression, expected); err != nil {
|
||||||
|
@ -63,8 +64,9 @@ func TestParamParser(t *testing.T) {
|
||||||
|
|
||||||
// id:int range(1,5)
|
// id:int range(1,5)
|
||||||
expected = &ParamTmpl{
|
expected = &ParamTmpl{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
Expression: "int range(1,5)",
|
Expression: "int range(1,5)",
|
||||||
|
FailStatusCode: 404,
|
||||||
Macro: MacroTmpl{Name: "int",
|
Macro: MacroTmpl{Name: "int",
|
||||||
Funcs: []MacroFuncTmpl{
|
Funcs: []MacroFuncTmpl{
|
||||||
MacroFuncTmpl{Name: "range", Params: []string{"1", "5"}},
|
MacroFuncTmpl{Name: "range", Params: []string{"1", "5"}},
|
||||||
|
@ -79,8 +81,9 @@ func TestParamParser(t *testing.T) {
|
||||||
|
|
||||||
// id:int min(1) max(5)
|
// id:int min(1) max(5)
|
||||||
expected = &ParamTmpl{
|
expected = &ParamTmpl{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
Expression: "int min(1) max(5)",
|
Expression: "int min(1) max(5)",
|
||||||
|
FailStatusCode: 404,
|
||||||
Macro: MacroTmpl{Name: "int",
|
Macro: MacroTmpl{Name: "int",
|
||||||
Funcs: []MacroFuncTmpl{
|
Funcs: []MacroFuncTmpl{
|
||||||
MacroFuncTmpl{Name: "min", Params: []string{"1"}},
|
MacroFuncTmpl{Name: "min", Params: []string{"1"}},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user