package parser import ( "fmt" "reflect" "strings" "testing" "github.com/kataras/iris/v12/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 := '$' input := "{id" + string(illegalChar) + "int range(1,5) else 404}" p := NewParamParser(input) _, err := p.Parse(testParamTypes) if err == nil { t.Fatalf("expecting not empty error on input '%s'", input) } illIdx := strings.IndexRune(input, illegalChar) expectedErr := fmt.Sprintf("[%d:%d] illegal token: %s", illIdx, illIdx, "$") if got := err.Error(); got != expectedErr { t.Fatalf("expecting error to be '%s' but got: %s", expectedErr, got) } // // success input2 := "{id:uint64 range(1,5) else 404}" p.Reset(input2) _, err = p.Parse(testParamTypes) if err != nil { t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error()) } // } // 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, testParamTypes...) if !found { panic("param type '" + indent + "' is not part of the provided param types") } return pt } func TestParseParam(t *testing.T) { tests := []struct { valid bool expectedStatement ast.ParamStatement }{ { true, ast.ParamStatement{ Src: "{id:int min(1) max(5) else 404}", Name: "id", Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "min", Args: []string{"1"}, }, { Name: "max", Args: []string{"5"}, }, }, ErrorCode: 404, }, }, // 0 { true, ast.ParamStatement{ // test alias of int. Src: "{id:number range(1,5)}", Name: "id", Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "range", Args: []string{"1", "5"}, }, }, ErrorCode: 404, }, }, // 1 { true, ast.ParamStatement{ Src: "{file:path contains(.)}", Name: "file", Type: mustLookupParamType("path"), Funcs: []ast.ParamFunc{ { Name: "contains", Args: []string{"."}, }, }, ErrorCode: 404, }, }, // 2 { true, ast.ParamStatement{ Src: "{username:alphabetical}", Name: "username", Type: mustLookupParamType("alphabetical"), ErrorCode: 404, }, }, // 3 { true, ast.ParamStatement{ Src: "{myparam}", Name: "myparam", Type: mustLookupParamType("string"), ErrorCode: 404, }, }, // 4 { false, ast.ParamStatement{ Src: "{myparam_:thisianunexpected}", Name: "myparam_", Type: nil, ErrorCode: 404, }, }, // 5 { true, ast.ParamStatement{ Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. Type: ast.GetMasterParamType(testParamTypes...), ErrorCode: 404, }, }, // 6 { true, ast.ParamStatement{ Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) Name: "id", Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "even", }, }, ErrorCode: 404, }, }, // 7 { true, ast.ParamStatement{ Src: "{id:int64 else 404}", Name: "id", Type: mustLookupParamType("int64"), ErrorCode: 404, }, }, // 8 { true, ast.ParamStatement{ Src: "{id:long else 404}", // backwards-compatible test. Name: "id", Type: mustLookupParamType("int64"), ErrorCode: 404, }, }, // 9 { true, ast.ParamStatement{ Src: "{id:long else 404}", Name: "id", Type: mustLookupParamType("int64"), // backwards-compatible test of LookupParamType. ErrorCode: 404, }, }, // 10 { true, ast.ParamStatement{ Src: "{has:bool else 404}", Name: "has", Type: mustLookupParamType("bool"), ErrorCode: 404, }, }, // 11 { true, ast.ParamStatement{ Src: "{has:boolean else 404}", // backwards-compatible test. Name: "has", Type: mustLookupParamType("bool"), ErrorCode: 404, }, }, // 12 } p := new(ParamParser) for i, tt := range tests { p.Reset(tt.expectedStatement.Src) resultStmt, err := p.Parse(testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) } else if !tt.valid && err == nil { t.Fatalf("tests[%d] - expected to be a failure", i) } if resultStmt != nil { // is valid here if !reflect.DeepEqual(tt.expectedStatement, *resultStmt) { t.Fatalf("tests[%d] - wrong statement, expected and result differs. Details:\n%#v\n%#v", i, tt.expectedStatement, *resultStmt) } } } } func TestParse(t *testing.T) { tests := []struct { path string valid bool expectedStatements []ast.ParamStatement }{ { "/api/users/{id:int min(1) max(5) else 404}", true, []ast.ParamStatement{ { Src: "{id:int min(1) max(5) else 404}", Name: "id", Type: paramTypeNumber, Funcs: []ast.ParamFunc{ { Name: "min", Args: []string{"1"}, }, { Name: "max", Args: []string{"5"}, }, }, ErrorCode: 404, }, }, }, // 0 { "/admin/{id:uint64 range(1,5)}", true, []ast.ParamStatement{ { Src: "{id:uint64 range(1,5)}", Name: "id", Type: paramTypeUint64, Funcs: []ast.ParamFunc{ { Name: "range", Args: []string{"1", "5"}, }, }, ErrorCode: 404, }, }, }, // 1 { "/files/{file:path contains(.)}", true, []ast.ParamStatement{ { Src: "{file:path contains(.)}", Name: "file", Type: paramTypePath, Funcs: []ast.ParamFunc{ { Name: "contains", Args: []string{"."}, }, }, ErrorCode: 404, }, }, }, // 2 { "/profile/{username:alphabetical}", true, []ast.ParamStatement{ { Src: "{username:alphabetical}", Name: "username", Type: paramTypeAlphabetical, ErrorCode: 404, }, }, }, // 3 { "/something/here/{myparam}", true, []ast.ParamStatement{ { Src: "{myparam}", Name: "myparam", Type: paramTypeString, ErrorCode: 404, }, }, }, // 4 { "/unexpected/{myparam_:thisianunexpected}", false, []ast.ParamStatement{ { Src: "{myparam_:thisianunexpected}", Name: "myparam_", Type: nil, ErrorCode: 404, }, }, }, // 5 { "/p2/{myparam2}", true, []ast.ParamStatement{ { Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. Type: paramTypeString, ErrorCode: 404, }, }, }, // 6 { "/assets/{file:path}/invalid", false, // path should be in the end segment []ast.ParamStatement{ { Src: "{file:path}", Name: "file", Type: paramTypePath, ErrorCode: 404, }, }, }, // 7 } for i, tt := range tests { statements, err := Parse(tt.path, testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) } else if !tt.valid && err == nil { t.Fatalf("tests[%d] - expected to be a failure", i) } for j := range statements { for l := range tt.expectedStatements { if !reflect.DeepEqual(tt.expectedStatements[l], *statements[j]) { t.Fatalf("tests[%d] - wrong statements, expected and result differs. Details:\n%#v\n%#v", i, tt.expectedStatements[l], *statements[j]) } } } } }