1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-22 20:37:05 +00:00

add the ability to add custom parameter types to the interpreter and mapped macros with any number of macro functions - example added - although it's working it is not ready yet - I have to do some cleanup, doc comments and a TODO

Former-commit-id: 8ac751b649a3b8e59948fd4c89ad53d25f49d0d5
This commit is contained in:
Gerasimos (Makis) Maropoulos
2018-09-26 11:37:11 +03:00
parent 52a07df0f4
commit dc3c38b189
26 changed files with 1070 additions and 1036 deletions

View File

@@ -1,43 +1,68 @@
package ast
import (
"reflect"
"strings"
type (
// ParamType holds the necessary information about a parameter type for the parser to lookup for.
ParamType interface {
// The name of the parameter type.
// Indent should contain the characters for the parser.
Indent() string
}
// MasterParamType if implemented and its `Master()` returns true then empty type param will be translated to this param type.
// Also its functions will be available to the rest of the macro param type's funcs.
//
// Only one Master is allowed.
MasterParamType interface {
ParamType
Master() bool
}
// TrailingParamType if implemented and its `Trailing()` returns true
// then it should be declared at the end of a route path and can accept any trailing path segment as one parameter.
TrailingParamType interface {
ParamType
Trailing() bool
}
// AliasParamType if implemeneted nad its `Alias()` returns a non-empty string
// then the param type can be written with that string literal too.
AliasParamType interface {
ParamType
Alias() string
}
)
// ParamType holds the necessary information about a parameter type.
type ParamType struct {
Indent string // the name of the parameter type.
Aliases []string // any aliases, can be empty.
GoType reflect.Kind // the go type useful for "mvc" and "hero" bindings.
Default bool // if true then empty type param will target this and its functions will be available to the rest of the param type's funcs.
End bool // if true then it should be declared at the end of a route path and can accept any trailing path segment as one parameter.
invalid bool // only true if returned by the parser via `LookupParamType`.
// IsMaster returns true if the "pt" param type is a master one.
func IsMaster(pt ParamType) bool {
p, ok := pt.(MasterParamType)
return ok && p.Master()
}
// ParamTypeUnExpected is the unexpected parameter type.
var ParamTypeUnExpected = ParamType{invalid: true}
func (pt ParamType) String() string {
return pt.Indent
// IsTrailing returns true if the "pt" param type is a marked as trailing,
// which should accept more than one path segment when in the end.
func IsTrailing(pt ParamType) bool {
p, ok := pt.(TrailingParamType)
return ok && p.Trailing()
}
// Assignable returns true if the "k" standard type
// is assignabled to this ParamType.
func (pt ParamType) Assignable(k reflect.Kind) bool {
return pt.GoType == k
// HasAlias returns any alias of the "pt" param type.
// If alias is empty or not found then it returns false as its second output argument.
func HasAlias(pt ParamType) (string, bool) {
if p, ok := pt.(AliasParamType); ok {
alias := p.Alias()
return alias, len(alias) > 0
}
return "", false
}
// GetDefaultParamType accepts a list of ParamType and returns its default.
// If no `Default` specified:
// GetMasterParamType accepts a list of ParamType and returns its master.
// If no `Master` specified:
// and len(paramTypes) > 0 then it will return the first one,
// otherwise it returns a "string" parameter type.
func GetDefaultParamType(paramTypes ...ParamType) ParamType {
// otherwise it returns nil.
func GetMasterParamType(paramTypes ...ParamType) ParamType {
for _, pt := range paramTypes {
if pt.Default == true {
if IsMaster(pt) {
return pt
}
}
@@ -46,24 +71,12 @@ func GetDefaultParamType(paramTypes ...ParamType) ParamType {
return paramTypes[0]
}
return ParamType{Indent: "string", GoType: reflect.String, Default: true}
}
// ValidKind will return true if at least one param type is supported
// for this std kind.
func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool {
for _, pt := range paramTypes {
if pt.GoType == k {
return true
}
}
return false
return nil
}
// LookupParamType accepts the string
// representation of a parameter type.
// Available:
// Example:
// "string"
// "number" or "int"
// "long" or "int64"
@@ -73,41 +86,20 @@ func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool {
// "alphabetical"
// "file"
// "path"
func LookupParamType(indent string, paramTypes ...ParamType) (ParamType, bool) {
func LookupParamType(indentOrAlias string, paramTypes ...ParamType) (ParamType, bool) {
for _, pt := range paramTypes {
if pt.Indent == indent {
if pt.Indent() == indentOrAlias {
return pt, true
}
for _, alias := range pt.Aliases {
if alias == indent {
if alias, has := HasAlias(pt); has {
if alias == indentOrAlias {
return pt, true
}
}
}
return ParamTypeUnExpected, false
}
// LookupParamTypeFromStd accepts the string representation of a standard go type.
// It returns a ParamType, but it may differs for example
// the alphabetical, file, path and string are all string go types, so
// make sure that caller resolves these types before this call.
//
// string matches to string
// int matches to int/number
// int64 matches to int64/long
// uint64 matches to uint64
// bool matches to bool/boolean
func LookupParamTypeFromStd(goType string, paramTypes ...ParamType) (ParamType, bool) {
goType = strings.ToLower(goType)
for _, pt := range paramTypes {
if strings.ToLower(pt.GoType.String()) == goType {
return pt, true
}
}
return ParamTypeUnExpected, false
return nil, false
}
// ParamStatement is a struct

View File

@@ -2,7 +2,6 @@ package parser
import (
"fmt"
"reflect"
"strconv"
"strings"
@@ -11,67 +10,12 @@ import (
"github.com/kataras/iris/core/router/macro/interpreter/token"
)
var (
// paramTypeString is the string type.
// If parameter type is missing then it defaults to String type.
// Allows anything
// Declaration: /mypath/{myparam:string} or {myparam}
paramTypeString = ast.ParamType{Indent: "string", GoType: reflect.String, Default: true}
// ParamTypeNumber is the integer, a number type.
// Allows both positive and negative numbers, any number of digits.
// Declaration: /mypath/{myparam:number} or {myparam:int} for backwards-compatibility
paramTypeNumber = ast.ParamType{Indent: "number", Aliases: []string{"int"}, GoType: reflect.Int}
// ParamTypeInt64 is a number type.
// Allows only -9223372036854775808 to 9223372036854775807.
// Declaration: /mypath/{myparam:int64} or {myparam:long}
paramTypeInt64 = ast.ParamType{Indent: "int64", Aliases: []string{"long"}, GoType: reflect.Int64}
// ParamTypeUint8 a number type.
// Allows only 0 to 255.
// Declaration: /mypath/{myparam:uint8}
paramTypeUint8 = ast.ParamType{Indent: "uint8", GoType: reflect.Uint8}
// ParamTypeUint64 a number type.
// Allows only 0 to 18446744073709551615.
// Declaration: /mypath/{myparam:uint64}
paramTypeUint64 = ast.ParamType{Indent: "uint64", GoType: reflect.Uint64}
// ParamTypeBool is the bool type.
// Allows only "1" or "t" or "T" or "TRUE" or "true" or "True"
// or "0" or "f" or "F" or "FALSE" or "false" or "False".
// Declaration: /mypath/{myparam:bool} or {myparam:boolean}
paramTypeBool = ast.ParamType{Indent: "bool", Aliases: []string{"boolean"}, GoType: reflect.Bool}
// ParamTypeAlphabetical is the alphabetical/letter type type.
// Allows letters only (upper or lowercase)
// Declaration: /mypath/{myparam:alphabetical}
paramTypeAlphabetical = ast.ParamType{Indent: "alphabetical", GoType: reflect.String}
// ParamTypeFile is the file single path type.
// Allows:
// letters (upper or lowercase)
// numbers (0-9)
// underscore (_)
// dash (-)
// point (.)
// no spaces! or other character
// Declaration: /mypath/{myparam:file}
paramTypeFile = ast.ParamType{Indent: "file", GoType: reflect.String}
// ParamTypePath is the multi path (or wildcard) type.
// Allows anything, should be the last part
// Declaration: /mypath/{myparam:path}
paramTypePath = ast.ParamType{Indent: "path", GoType: reflect.String, End: true}
)
// DefaultParamTypes are the built'n parameter types.
var DefaultParamTypes = []ast.ParamType{
paramTypeString,
paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64,
paramTypeBool,
paramTypeAlphabetical, paramTypeFile, paramTypePath,
}
// Parse takes a route "fullpath"
// and returns its param statements
// and an error on failure.
func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, error) {
// or an error if failed.
func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, error) {
if len(paramTypes) == 0 {
paramTypes = DefaultParamTypes
return nil, fmt.Errorf("empty parameter types")
}
pathParts := strings.SplitN(fullpath, "/", -1)
@@ -94,7 +38,7 @@ func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement,
return nil, err
}
// if we have param type path but it's not the last path part
if stmt.Type.End && i < len(pathParts)-1 {
if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 {
return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s)
}
@@ -166,7 +110,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er
stmt := &ast.ParamStatement{
ErrorCode: DefaultParamErrorCode,
Type: ast.GetDefaultParamType(paramTypes...),
Type: ast.GetMasterParamType(paramTypes...),
Src: p.src,
}
@@ -190,6 +134,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er
// type can accept both letters and numbers but not symbols ofc.
nextTok := l.NextToken()
paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...)
if !found {
p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal)
}

View File

@@ -9,6 +9,44 @@ import (
"github.com/kataras/iris/core/router/macro/interpreter/ast"
)
type simpleParamType string
func (pt simpleParamType) Indent() string { return string(pt) }
type masterParamType simpleParamType
func (pt masterParamType) Indent() string { return string(pt) }
func (pt masterParamType) Master() bool { return true }
type wildcardParamType string
func (pt wildcardParamType) Indent() string { return string(pt) }
func (pt wildcardParamType) Trailing() bool { return true }
type aliasedParamType []string
func (pt aliasedParamType) Indent() string { return string(pt[0]) }
func (pt aliasedParamType) Alias() string { return pt[1] }
var (
paramTypeString = masterParamType("string")
paramTypeNumber = aliasedParamType{"number", "int"}
paramTypeInt64 = aliasedParamType{"int64", "long"}
paramTypeUint8 = simpleParamType("uint8")
paramTypeUint64 = simpleParamType("uint64")
paramTypeBool = aliasedParamType{"bool", "boolean"}
paramTypeAlphabetical = simpleParamType("alphabetical")
paramTypeFile = simpleParamType("file")
paramTypePath = wildcardParamType("path")
)
var testParamTypes = []ast.ParamType{
paramTypeString,
paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64,
paramTypeBool,
paramTypeAlphabetical, paramTypeFile, paramTypePath,
}
func TestParseParamError(t *testing.T) {
// fail
illegalChar := '$'
@@ -16,7 +54,7 @@ func TestParseParamError(t *testing.T) {
input := "{id" + string(illegalChar) + "int range(1,5) else 404}"
p := NewParamParser(input)
_, err := p.Parse(DefaultParamTypes)
_, err := p.Parse(testParamTypes)
if err == nil {
t.Fatalf("expecting not empty error on input '%s'", input)
@@ -32,7 +70,7 @@ func TestParseParamError(t *testing.T) {
// success
input2 := "{id:uint64 range(1,5) else 404}"
p.Reset(input2)
_, err = p.Parse(DefaultParamTypes)
_, err = p.Parse(testParamTypes)
if err != nil {
t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error())
@@ -42,7 +80,7 @@ func TestParseParamError(t *testing.T) {
// mustLookupParamType same as `ast.LookupParamType` but it panics if "indent" does not match with a valid Param Type.
func mustLookupParamType(indent string) ast.ParamType {
pt, found := ast.LookupParamType(indent, DefaultParamTypes...)
pt, found := ast.LookupParamType(indent, testParamTypes...)
if !found {
panic("param type '" + indent + "' is not part of the provided param types")
}
@@ -113,14 +151,14 @@ func TestParseParam(t *testing.T) {
ast.ParamStatement{
Src: "{myparam_:thisianunexpected}",
Name: "myparam_",
Type: ast.ParamTypeUnExpected,
Type: nil,
ErrorCode: 404,
}}, // 5
{true,
ast.ParamStatement{
Src: "{myparam2}",
Name: "myparam2", // we now allow integers to the parameter names.
Type: ast.GetDefaultParamType(DefaultParamTypes...),
Type: ast.GetMasterParamType(testParamTypes...),
ErrorCode: 404,
}}, // 6
{true,
@@ -152,7 +190,7 @@ func TestParseParam(t *testing.T) {
ast.ParamStatement{
Src: "{id:long else 404}",
Name: "id",
Type: mustLookupParamType("long"), // backwards-compatible test of LookupParamType.
Type: mustLookupParamType("int64"), // backwards-compatible test of LookupParamType.
ErrorCode: 404,
}}, // 10
{true,
@@ -175,7 +213,7 @@ func TestParseParam(t *testing.T) {
p := new(ParamParser)
for i, tt := range tests {
p.Reset(tt.expectedStatement.Src)
resultStmt, err := p.Parse(DefaultParamTypes)
resultStmt, err := p.Parse(testParamTypes)
if tt.valid && err != nil {
t.Fatalf("tests[%d] - error %s", i, err.Error())
@@ -216,7 +254,7 @@ func TestParse(t *testing.T) {
}}, // 0
{"/admin/{id:uint64 range(1,5)}", true,
[]ast.ParamStatement{{
Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int"
Src: "{id:uint64 range(1,5)}",
Name: "id",
Type: paramTypeUint64,
Funcs: []ast.ParamFunc{
@@ -260,7 +298,7 @@ func TestParse(t *testing.T) {
[]ast.ParamStatement{{
Src: "{myparam_:thisianunexpected}",
Name: "myparam_",
Type: ast.ParamTypeUnExpected,
Type: nil,
ErrorCode: 404,
},
}}, // 5
@@ -282,7 +320,7 @@ func TestParse(t *testing.T) {
}}, // 7
}
for i, tt := range tests {
statements, err := Parse(tt.path)
statements, err := Parse(tt.path, testParamTypes)
if tt.valid && err != nil {
t.Fatalf("tests[%d] - error %s", i, err.Error())