From 493f3bad0f63d4d950c8abc9d1c9a19cdf4e87e9 Mon Sep 17 00:00:00 2001 From: Kevin Wan Date: Sun, 4 May 2025 11:07:43 +0800 Subject: [PATCH] fix: allow special characters like periods in API route paths (#4827) --- tools/goctl/pkg/parser/api/parser/parser.go | 14 +++++++ .../pkg/parser/api/parser/parser_test.go | 40 ++++++++++++++++--- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/tools/goctl/pkg/parser/api/parser/parser.go b/tools/goctl/pkg/parser/api/parser/parser.go index 6befab582..c66f31c1e 100644 --- a/tools/goctl/pkg/parser/api/parser/parser.go +++ b/tools/goctl/pkg/parser/api/parser/parser.go @@ -465,6 +465,20 @@ func (p *Parser) parsePathItem() []token.Token { if !p.advanceIfPeekTokenIs(token.IDENT) { return nil } + list = append(list, p.curTok) + } else if p.peekTokenIs(token.DOT) { + // Allow dot (.) in path segments for file extensions like .php, .html, etc. + if !p.nextToken() { + return nil + } + + list = append(list, p.curTok) + + // After a dot, we expect an identifier (e.g., .php, .html) + if !p.advanceIfPeekTokenIs(token.IDENT) { + return nil + } + list = append(list, p.curTok) } else { if p.peekTokenIs(token.LPAREN, token.Returns, token.AT_DOC, token.AT_HANDLER, token.SEMICOLON, token.RBRACE) { diff --git a/tools/goctl/pkg/parser/api/parser/parser_test.go b/tools/goctl/pkg/parser/api/parser/parser_test.go index 4d1cd9f61..61c3c828c 100644 --- a/tools/goctl/pkg/parser/api/parser/parser_test.go +++ b/tools/goctl/pkg/parser/api/parser/parser_test.go @@ -760,11 +760,6 @@ func TestParser_Parse_service(t *testing.T) { }), }, Request: &ast.BodyStmt{ - LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}), - RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}), - }, - Returns: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "returns"}), - Response: &ast.BodyStmt{ LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}), Body: &ast.BodyExpr{ Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}), @@ -1031,6 +1026,7 @@ func TestParser_Parse_pathItem(t *testing.T) { {input: "1", expected: "1"}, {input: "11", expected: "11"}, } + for _, v := range testData { p := New("foo.api", v.input) ok := p.nextToken() @@ -1066,6 +1062,38 @@ func TestParser_Parse_pathItem(t *testing.T) { }) } +func TestParser_Parse_pathItem_WithDot(t *testing.T) { + t.Run("valid with dots", func(t *testing.T) { + var testData = []struct { + input string + expected string + }{ + {input: "file.php", expected: "file.php"}, + {input: "api_jsonrpc.php", expected: "api_jsonrpc.php"}, + {input: "index.html", expected: "index.html"}, + {input: "data.json", expected: "data.json"}, + {input: "style.css", expected: "style.css"}, + {input: "script.js", expected: "script.js"}, + {input: "document.pdf", expected: "document.pdf"}, + {input: "image.png", expected: "image.png"}, + {input: "api.v1", expected: "api.v1"}, + {input: "resource.with.multiple.dots", expected: "resource.with.multiple.dots"}, + } + + for _, v := range testData { + p := New("foo.api", v.input) + ok := p.nextToken() + assert.True(t, ok) + tokens := p.parsePathItem() + var expected []string + for _, tok := range tokens { + expected = append(expected, tok.Text) + } + assert.Equal(t, strings.Join(expected, ""), v.expected) + } + }) +} + func TestParser_Parse_parseTypeStmt(t *testing.T) { assertEqual := func(t *testing.T, expected, actual ast.Stmt) { if expected == nil { @@ -1399,6 +1427,7 @@ func TestParser_Parse_parseTypeStmt(t *testing.T) { assertEqual(t, val.expected, one) } }) + t.Run("parseTypeGroupStmt", func(t *testing.T) { var testData = []struct { input string @@ -1472,6 +1501,7 @@ func TestParser_Parse_parseTypeStmt(t *testing.T) { }, }, } + for _, val := range testData { p := New("test.api", val.input) result := p.Parse()