mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
_future
1. Fix index, including both start and end. So Literal[start:end+1] will be a valid part. 2. Replace any with string, add file param type 3. Start of making the evaluator, starting with regexp for param types (these expression can be changed or/and overriden by user later on) Former-commit-id: ab95265f953dadbf84170b543e1ff8840f9c4a14
This commit is contained in:
parent
251eeb6bd0
commit
126c4de29b
|
@ -7,13 +7,13 @@
|
||||||
- No Breaking Changes.
|
- No Breaking Changes.
|
||||||
- No performance cost if not used.
|
- No performance cost if not used.
|
||||||
- Can convert a path for the existing routers, if no router is being used, then it will use its own, new, router.
|
- Can convert a path for the existing routers, if no router is being used, then it will use its own, new, router.
|
||||||
- 4+1 basic parameter types: `int`, `string`, `alphabetical`, `path`, (wildcard), `any` based on regexp.
|
- 4+1 basic parameter types: `string`, `int`, `alphabetical`, `file`, `path` (file with any number of slashes), based on regexp.
|
||||||
- Each type has unlimited functions of its own, they should be able to be overriden.
|
- Each type has unlimited functions of its own, they should be able to be overriden.
|
||||||
- Give devs the ability to parse their function's arguments before use them and return a func which is the validator.
|
- Give devs the ability to parse their function's arguments before use them and return a func which is the validator.
|
||||||
- Function will be a generic type(`interface{}`) in order to devs be able to use any type without boilerplate code for conversions,
|
- Function will be a generic type(`interface{}`) in order to devs be able to use any type without boilerplate code for conversions,
|
||||||
can be done using reflection and reflect.Call, on .Boot time to parse the function automatically, and keep the returning validator function (already tested and worked).
|
can be done using reflection and reflect.Call, on .Boot time to parse the function automatically, and keep the returning validator function (already tested and worked).
|
||||||
- The `any` will be the default if dev use functions to the named path parameter but missing a type.
|
- The `string` will be the default if dev use functions to the named path parameter but missing a type.
|
||||||
- If a type doesnt't contains a function of its own, then it will use the `any`'s, so `any` will contain global-use functions too.
|
- If a type doesnt't contains a function of its own, then it will use the `string`'s, so `string` will contain global-use functions too.
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ app.String.Set("isVersion", isVersionStrValidator)
|
||||||
app.String.Set("len", lenStrValidator)
|
app.String.Set("len", lenStrValidator)
|
||||||
```
|
```
|
||||||
|
|
||||||
`/uploads/{filepath:tail contains(.) else 403}`
|
`/uploads/{fullpath:path contains(.) else 403}`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
[...]
|
[...]
|
||||||
|
|
|
@ -4,25 +4,29 @@ type ParamType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ParamTypeUnExpected ParamType = iota
|
ParamTypeUnExpected ParamType = iota
|
||||||
// /42
|
|
||||||
ParamTypeInt
|
|
||||||
// /myparam1
|
// /myparam1
|
||||||
ParamTypeString
|
ParamTypeString
|
||||||
|
// /42
|
||||||
|
ParamTypeInt
|
||||||
// /myparam
|
// /myparam
|
||||||
ParamTypeAlphabetical
|
ParamTypeAlphabetical
|
||||||
|
// /main.css
|
||||||
|
ParamTypeFile
|
||||||
// /myparam1/myparam2
|
// /myparam1/myparam2
|
||||||
ParamTypePath
|
ParamTypePath
|
||||||
)
|
)
|
||||||
|
|
||||||
var paramTypes = map[string]ParamType{
|
var paramTypes = map[string]ParamType{
|
||||||
"int": ParamTypeInt,
|
|
||||||
"string": ParamTypeString,
|
"string": ParamTypeString,
|
||||||
|
"int": ParamTypeInt,
|
||||||
"alphabetical": ParamTypeAlphabetical,
|
"alphabetical": ParamTypeAlphabetical,
|
||||||
|
"file": ParamTypeFile,
|
||||||
"path": ParamTypePath,
|
"path": ParamTypePath,
|
||||||
// could be named also:
|
// could be named also:
|
||||||
// "tail":
|
// "tail":
|
||||||
// "wild"
|
// "wild"
|
||||||
// "wildcard"
|
// "wildcard"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupParamType(ident string) ParamType {
|
func LookupParamType(ident string) ParamType {
|
||||||
|
|
27
_future/ipel/evaluator/evaluator.go
Normal file
27
_future/ipel/evaluator/evaluator.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package evaluator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// final evaluator signature for both param types and param funcs
|
||||||
|
type ParamEvaluator func(paramValue string) bool
|
||||||
|
|
||||||
|
func NewParamEvaluatorFromRegexp(expr string) (ParamEvaluator, error) {
|
||||||
|
if expr == "" {
|
||||||
|
return nil, fmt.Errorf("empty regex expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the last $ if missing (and not wildcard(?))
|
||||||
|
if i := expr[len(expr)-1]; i != '$' && i != '*' {
|
||||||
|
expr += "$"
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := regexp.Compile(expr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.MatchString, nil
|
||||||
|
}
|
56
_future/ipel/evaluator/param.go
Normal file
56
_future/ipel/evaluator/param.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package evaluator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/kataras/iris.v6/_future/ipel/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// exported to be able to change how param types are evaluating
|
||||||
|
var ParamTypeEvaluator = make(map[ast.ParamType]ParamEvaluator, 0)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
// string type
|
||||||
|
// anything.
|
||||||
|
stringRegex, err := NewParamEvaluatorFromRegexp(".*")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ParamTypeEvaluator[ast.ParamTypeString] = stringRegex
|
||||||
|
|
||||||
|
// int type
|
||||||
|
// only numbers (0-9)
|
||||||
|
numRegex, err := NewParamEvaluatorFromRegexp("[0-9]+$")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ParamTypeEvaluator[ast.ParamTypeInt] = numRegex
|
||||||
|
|
||||||
|
// alphabetical/letter type
|
||||||
|
// letters only (upper or lowercase)
|
||||||
|
alphabeticRegex, err := NewParamEvaluatorFromRegexp("[a-zA-Z]+$")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ParamTypeEvaluator[ast.ParamTypeAlphabetical] = alphabeticRegex
|
||||||
|
|
||||||
|
// file type
|
||||||
|
// letters (upper or lowercase)
|
||||||
|
// numbers (0-9)
|
||||||
|
// underscore (_)
|
||||||
|
// dash (-)
|
||||||
|
// point (.)
|
||||||
|
// no spaces! or other character
|
||||||
|
fileRegex, err := NewParamEvaluatorFromRegexp("[a-zA-Z0-9_.-]*$")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ParamTypeEvaluator[ast.ParamTypeFile] = fileRegex
|
||||||
|
|
||||||
|
// path type
|
||||||
|
// file with slashes(anywhere)
|
||||||
|
pathRegex, err := NewParamEvaluatorFromRegexp("[a-zA-Z0-9_.-/]*$")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ParamTypeEvaluator[ast.ParamTypePath] = pathRegex
|
||||||
|
}
|
|
@ -11,9 +11,9 @@ type Lexer struct {
|
||||||
ch byte // current char under examination
|
ch byte // current char under examination
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(input string) *Lexer {
|
func New(src string) *Lexer {
|
||||||
l := &Lexer{
|
l := &Lexer{
|
||||||
input: input,
|
input: src,
|
||||||
}
|
}
|
||||||
// step to the first character in order to be ready
|
// step to the first character in order to be ready
|
||||||
l.readChar()
|
l.readChar()
|
||||||
|
@ -86,9 +86,12 @@ func (l *Lexer) newToken(tokenType token.TokenType, lit string) token.Token {
|
||||||
Start: l.pos,
|
Start: l.pos,
|
||||||
End: l.pos,
|
End: l.pos,
|
||||||
}
|
}
|
||||||
|
// remember, l.pos is the last char
|
||||||
|
// and we want to include both start and end
|
||||||
|
// in order to be easy to the user to see by just marking the expression
|
||||||
if l.pos > 1 && len(lit) > 1 {
|
if l.pos > 1 && len(lit) > 1 {
|
||||||
t.End = t.Start + len(lit) - 1
|
t.End = l.pos - 1
|
||||||
|
t.Start = t.End - len(lit) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
|
@ -15,19 +15,23 @@ type Parser struct {
|
||||||
errors []string
|
errors []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(lexer *lexer.Lexer) *Parser {
|
func New(src string) *Parser {
|
||||||
p := &Parser{
|
p := new(Parser)
|
||||||
l: lexer,
|
p.Reset(src)
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Parser) Reset(src string) {
|
||||||
|
p.l = lexer.New(src)
|
||||||
|
p.errors = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Parser) appendErr(format string, a ...interface{}) {
|
func (p *Parser) appendErr(format string, a ...interface{}) {
|
||||||
p.errors = append(p.errors, fmt.Sprintf(format, a...))
|
p.errors = append(p.errors, fmt.Sprintf(format, a...))
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultParamErrorCode = 404
|
const DefaultParamErrorCode = 404
|
||||||
|
const DefaultParamType = ast.ParamTypeString
|
||||||
|
|
||||||
func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) {
|
func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) {
|
||||||
if t.Type == token.INT {
|
if t.Type == token.INT {
|
||||||
|
@ -36,20 +40,35 @@ func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) {
|
||||||
return t.Literal, nil
|
return t.Literal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Parser) Error() error {
|
||||||
|
if len(p.errors) > 0 {
|
||||||
|
return fmt.Errorf(strings.Join(p.errors, "\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Parser) Parse() (*ast.ParamStatement, error) {
|
func (p *Parser) Parse() (*ast.ParamStatement, error) {
|
||||||
stmt := new(ast.ParamStatement)
|
p.errors = []string{}
|
||||||
stmt.ErrorCode = DefaultParamErrorCode
|
|
||||||
// let's have them nilled stmt.Funcs = make([]ast.ParamFunc, 0)
|
stmt := &ast.ParamStatement{
|
||||||
|
ErrorCode: DefaultParamErrorCode,
|
||||||
|
Type: DefaultParamType,
|
||||||
|
}
|
||||||
|
|
||||||
lastParamFunc := ast.ParamFunc{}
|
lastParamFunc := ast.ParamFunc{}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
t := p.l.NextToken()
|
t := p.l.NextToken()
|
||||||
if t.Type == token.EOF {
|
if t.Type == token.EOF {
|
||||||
|
if stmt.Name == "" {
|
||||||
|
p.appendErr("[1:] parameter name is missing")
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t.Type {
|
switch t.Type {
|
||||||
case token.LBRACE:
|
case token.LBRACE:
|
||||||
// name
|
// name, alphabetical and _, param names are not allowed to contain any number.
|
||||||
nextTok := p.l.NextToken()
|
nextTok := p.l.NextToken()
|
||||||
stmt.Name = nextTok.Literal
|
stmt.Name = nextTok.Literal
|
||||||
case token.COLON:
|
case token.COLON:
|
||||||
|
@ -58,7 +77,6 @@ func (p *Parser) Parse() (*ast.ParamStatement, error) {
|
||||||
paramType := ast.LookupParamType(nextTok.Literal)
|
paramType := ast.LookupParamType(nextTok.Literal)
|
||||||
if paramType == ast.ParamTypeUnExpected {
|
if paramType == ast.ParamTypeUnExpected {
|
||||||
p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal)
|
p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
stmt.Type = paramType
|
stmt.Type = paramType
|
||||||
// param func
|
// param func
|
||||||
|
@ -104,12 +122,8 @@ func (p *Parser) Parse() (*ast.ParamStatement, error) {
|
||||||
p.appendErr("[%d:%d] illegal token: %s", t.Start, t.End, t.Literal)
|
p.appendErr("[%d:%d] illegal token: %s", t.Start, t.End, t.Literal)
|
||||||
default:
|
default:
|
||||||
p.appendErr("[%d:%d] unexpected token type: %q with value %s", t.Start, t.End, t.Type, t.Literal)
|
p.appendErr("[%d:%d] unexpected token type: %q with value %s", t.Start, t.End, t.Type, t.Literal)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.errors) > 0 {
|
return stmt, p.Error()
|
||||||
return nil, fmt.Errorf(strings.Join(p.errors, "\n"))
|
|
||||||
}
|
|
||||||
return stmt, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gopkg.in/kataras/iris.v6/_future/ipel/ast"
|
"gopkg.in/kataras/iris.v6/_future/ipel/ast"
|
||||||
"gopkg.in/kataras/iris.v6/_future/ipel/lexer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseError(t *testing.T) {
|
func TestParseError(t *testing.T) {
|
||||||
|
@ -15,8 +14,7 @@ func TestParseError(t *testing.T) {
|
||||||
illegalChar := '$'
|
illegalChar := '$'
|
||||||
|
|
||||||
input := "{id" + string(illegalChar) + "int range(1,5) else 404}"
|
input := "{id" + string(illegalChar) + "int range(1,5) else 404}"
|
||||||
l := lexer.New(input)
|
p := New(input)
|
||||||
p := New(l)
|
|
||||||
|
|
||||||
_, err := p.Parse()
|
_, err := p.Parse()
|
||||||
|
|
||||||
|
@ -36,10 +34,8 @@ func TestParseError(t *testing.T) {
|
||||||
|
|
||||||
// success
|
// success
|
||||||
input2 := "{id:int range(1,5) else 404}"
|
input2 := "{id:int range(1,5) else 404}"
|
||||||
l2 := lexer.New(input2)
|
p.Reset(input2)
|
||||||
p2 := New(l2)
|
_, err = p.Parse()
|
||||||
|
|
||||||
_, err = p2.Parse()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error())
|
t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error())
|
||||||
|
@ -89,23 +85,35 @@ func TestParse(t *testing.T) {
|
||||||
},
|
},
|
||||||
ErrorCode: 404,
|
ErrorCode: 404,
|
||||||
}}, // 2
|
}}, // 2
|
||||||
{"{username:alphabetical", true,
|
{"{username:alphabetical}", true,
|
||||||
ast.ParamStatement{
|
ast.ParamStatement{
|
||||||
Name: "username",
|
Name: "username",
|
||||||
Type: ast.ParamTypeAlphabetical,
|
Type: ast.ParamTypeAlphabetical,
|
||||||
ErrorCode: 404,
|
ErrorCode: 404,
|
||||||
}}, // 3
|
}}, // 3
|
||||||
{"{username:thisianunexpected", false,
|
{"{myparam}", true,
|
||||||
ast.ParamStatement{
|
ast.ParamStatement{
|
||||||
Name: "username",
|
Name: "myparam",
|
||||||
Type: ast.ParamTypeUnExpected,
|
Type: ast.ParamTypeString,
|
||||||
ErrorCode: 404,
|
ErrorCode: 404,
|
||||||
}}, // 4
|
}}, // 4
|
||||||
}
|
{"{myparam_:thisianunexpected}", false,
|
||||||
|
ast.ParamStatement{
|
||||||
|
Name: "myparam_",
|
||||||
|
Type: ast.ParamTypeUnExpected,
|
||||||
|
ErrorCode: 404,
|
||||||
|
}}, // 5
|
||||||
|
{"{myparam2}", false, // false because it will give an error of unexpeced token type with value 2
|
||||||
|
ast.ParamStatement{
|
||||||
|
Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names.
|
||||||
|
Type: ast.ParamTypeString,
|
||||||
|
ErrorCode: 404,
|
||||||
|
}}, // 6
|
||||||
|
|
||||||
|
}
|
||||||
|
var p *Parser = new(Parser)
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
l := lexer.New(tt.input)
|
p.Reset(tt.input)
|
||||||
p := New(l)
|
|
||||||
resultStmt, err := p.Parse()
|
resultStmt, err := p.Parse()
|
||||||
|
|
||||||
if tt.valid && err != nil {
|
if tt.valid && err != nil {
|
||||||
|
|
|
@ -5,24 +5,15 @@ type TokenType int
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Type TokenType
|
Type TokenType
|
||||||
Literal string
|
Literal string
|
||||||
Start int // excluding, useful for user
|
Start int // including the first char, Literal[index:]
|
||||||
End int // excluding, useful for user and index
|
End int // including the last char, Literal[start:end+1)
|
||||||
}
|
|
||||||
|
|
||||||
func (t Token) StartIndex() int {
|
|
||||||
if t.Start > 0 {
|
|
||||||
return t.Start + 1
|
|
||||||
}
|
|
||||||
return t.Start
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Token) EndIndex() int {
|
|
||||||
return t.End
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /about/{fullname:alphabetical}
|
||||||
|
// /profile/{anySpecialName:string}
|
||||||
// {id:int range(1,5) else 404}
|
// {id:int range(1,5) else 404}
|
||||||
// /admin/{id:int eq(1) else 402}
|
// /admin/{id:int eq(1) else 402}
|
||||||
// /file/{filepath:tail else 405}
|
// /file/{filepath:file else 405}
|
||||||
const (
|
const (
|
||||||
EOF = iota // 0
|
EOF = iota // 0
|
||||||
ILLEGAL
|
ILLEGAL
|
||||||
|
@ -33,7 +24,7 @@ const (
|
||||||
// PARAM_IDENTIFIER // id
|
// PARAM_IDENTIFIER // id
|
||||||
COLON // :
|
COLON // :
|
||||||
// let's take them in parser
|
// let's take them in parser
|
||||||
// PARAM_TYPE // int, string, alphabetic, tail
|
// PARAM_TYPE // int, string, alphabetical, file, path or unexpected
|
||||||
// PARAM_FUNC // range
|
// PARAM_FUNC // range
|
||||||
LPAREN // (
|
LPAREN // (
|
||||||
RPAREN // )
|
RPAREN // )
|
||||||
|
|
Loading…
Reference in New Issue
Block a user