1485 lines
46 KiB
Go
1485 lines
46 KiB
Go
package parser
|
|
|
|
import (
|
|
_ "embed"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/assertx"
|
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
|
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
|
)
|
|
|
|
//go:embed testdata/comment_test.api
|
|
var testCommentInput string
|
|
|
|
func TestParser_init(t *testing.T) {
|
|
var testData = []string{
|
|
"`",
|
|
"@`",
|
|
"syntax/**/`",
|
|
}
|
|
for _, val := range testData {
|
|
p := New("test.api", val)
|
|
val := p.init()
|
|
assert.False(t, val)
|
|
}
|
|
}
|
|
|
|
//go:embed testdata/test.api
|
|
var testInput string
|
|
|
|
func TestParser_Parse(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) { // EXPERIMENTAL: just for testing output formatter.
|
|
p := New("test.api", testInput)
|
|
result := p.Parse()
|
|
assert.NotNil(t, result)
|
|
assert.True(t, p.hasNoErrors())
|
|
})
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
"foo bar",
|
|
"@",
|
|
}
|
|
for _, val := range testData {
|
|
p := New("test.api", val)
|
|
p.Parse()
|
|
assertx.ErrorOrigin(t, val, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParser_Parse_Mode(t *testing.T) {
|
|
|
|
t.Run("All", func(t *testing.T) {
|
|
var testData = []string{
|
|
`// foo`,
|
|
`// bar`,
|
|
`/*foo*/`,
|
|
`/*bar*/`,
|
|
`//baz`,
|
|
}
|
|
p := New("foo.api", testCommentInput)
|
|
result := p.Parse()
|
|
for idx, v := range testData {
|
|
stmt := result.Stmts[idx]
|
|
c, ok := stmt.(*ast.CommentStmt)
|
|
assert.True(t, ok)
|
|
assert.True(t, p.hasNoErrors())
|
|
assert.Equal(t, v, c.Format(""))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParser_Parse_syntaxStmt(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
var testData = []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
input: `syntax = "v1"`,
|
|
expected: `syntax = "v1"`,
|
|
},
|
|
{
|
|
input: `syntax = "foo"`,
|
|
expected: `syntax = "foo"`,
|
|
},
|
|
{
|
|
input: `syntax= "bar"`,
|
|
expected: `syntax = "bar"`,
|
|
},
|
|
{
|
|
input: ` syntax = "" `,
|
|
expected: `syntax = ""`,
|
|
},
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.aoi", v.input)
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
assert.Equal(t, v.expected, result.Stmts[0].Format(""))
|
|
}
|
|
})
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`syntax`,
|
|
`syntax = `,
|
|
`syntax = ''`,
|
|
`syntax = @`,
|
|
`syntax = "v1`,
|
|
`syntax == "v"`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.Parse()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/info_test.api
|
|
var infoTestAPI string
|
|
|
|
func TestParser_Parse_infoStmt(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
expected := map[string]string{
|
|
"title:": `"type title here"`,
|
|
"desc:": `"type desc here"`,
|
|
"author:": `"type author here"`,
|
|
"email:": `"type email here"`,
|
|
"version:": `"type version here"`,
|
|
}
|
|
p := New("foo.api", infoTestAPI)
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
stmt := result.Stmts[0]
|
|
infoStmt, ok := stmt.(*ast.InfoStmt)
|
|
assert.True(t, ok)
|
|
for _, stmt := range infoStmt.Values {
|
|
actual := stmt.Value.Token.Text
|
|
expectedValue := expected[stmt.Key.Token.Text]
|
|
assert.Equal(t, expectedValue, actual)
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("empty", func(t *testing.T) {
|
|
p := New("foo.api", "info ()")
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
stmt := result.Stmts[0]
|
|
infoStmt, ok := stmt.(*ast.InfoStmt)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, "info", infoStmt.Info.Token.Text)
|
|
assert.Equal(t, "(", infoStmt.LParen.Token.Text)
|
|
assert.Equal(t, ")", infoStmt.RParen.Token.Text)
|
|
assert.Equal(t, 0, len(infoStmt.Values))
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`info`,
|
|
`info(`,
|
|
`info{`,
|
|
`info(}`,
|
|
`info( foo`,
|
|
`info( foo:`,
|
|
`info( foo:""`,
|
|
`info( foo:"" bar`,
|
|
`info( foo:"" bar:`,
|
|
`info( foo:"" bar:""`,
|
|
`info( foo:"`,
|
|
`info foo:""`,
|
|
`info( foo,""`,
|
|
`info( foo-bar:"")`,
|
|
`info(123:"")`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.Parse()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/import_literal_test.api
|
|
var testImportLiteral string
|
|
|
|
func TestParser_Parse_importLiteral(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`""`,
|
|
`"foo"`,
|
|
`"bar"`,
|
|
}
|
|
p := New("foo.api", testImportLiteral)
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
for idx, v := range testData {
|
|
stmt := result.Stmts[idx]
|
|
importLit, ok := stmt.(*ast.ImportLiteralStmt)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, v, importLit.Value.Token.Text)
|
|
}
|
|
})
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`import`,
|
|
`import "`,
|
|
`import "foo`,
|
|
`import foo`,
|
|
`import @`,
|
|
`import $`,
|
|
`import 好`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.Parse()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/import_group_test.api
|
|
var testImportGroup string
|
|
|
|
func TestParser_Parse_importGroup(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`""`,
|
|
`"foo"`,
|
|
`"bar"`,
|
|
}
|
|
p := New("foo.api", testImportGroup)
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
stmt := result.Stmts[0]
|
|
importGroup, ok := stmt.(*ast.ImportGroupStmt)
|
|
assert.Equal(t, token.IDENT, importGroup.Import.Token.Type)
|
|
assert.Equal(t, token.LPAREN, importGroup.LParen.Token.Type)
|
|
assert.Equal(t, token.RPAREN, importGroup.RParen.Token.Type)
|
|
for idx, v := range testData {
|
|
assert.True(t, ok)
|
|
assert.Equal(t, v, importGroup.Values[idx].Token.Text)
|
|
}
|
|
})
|
|
|
|
t.Run("empty", func(t *testing.T) {
|
|
p := New("foo.api", "import ()")
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
stmt := result.Stmts[0]
|
|
importGroup, ok := stmt.(*ast.ImportGroupStmt)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, token.IDENT, importGroup.Import.Token.Type)
|
|
assert.Equal(t, token.LPAREN, importGroup.LParen.Token.Type)
|
|
assert.Equal(t, token.RPAREN, importGroup.RParen.Token.Type)
|
|
assert.Equal(t, 0, len(importGroup.Values))
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`import`,
|
|
`import (`,
|
|
`import {`,
|
|
`import ( "`,
|
|
`import (} "`,
|
|
`import ( ")`,
|
|
`import ( ""`,
|
|
`import ( "" foo)`,
|
|
`import ( "" 好)`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.Parse()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/atserver_test.api
|
|
var atServerTestAPI string
|
|
|
|
func TestParser_Parse_atServerStmt(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
var expectedData = map[string]string{
|
|
"foo:": `bar`,
|
|
"bar:": `baz`,
|
|
"baz:": `foo`,
|
|
"qux:": `/v1`,
|
|
"quux:": `/v1/v2`,
|
|
"middleware:": `M1,M2`,
|
|
"timeout1:": "1h",
|
|
"timeout2:": "10m",
|
|
"timeout3:": "10s",
|
|
"timeout4:": "10ms",
|
|
"timeout5:": "10µs",
|
|
"timeout6:": "10ns",
|
|
"timeout7:": "1h10m10s10ms10µs10ns",
|
|
"maxBytes:": `1024`,
|
|
}
|
|
|
|
p := New("foo.api", atServerTestAPI)
|
|
result := p.ParseForUintTest()
|
|
assert.True(t, p.hasNoErrors())
|
|
stmt := result.Stmts[0]
|
|
atServerStmt, ok := stmt.(*ast.AtServerStmt)
|
|
assert.True(t, ok)
|
|
for _, v := range atServerStmt.Values {
|
|
expectedValue := expectedData[v.Key.Token.Text]
|
|
assert.Equal(t, expectedValue, v.Value.Token.Text)
|
|
}
|
|
})
|
|
|
|
t.Run("empty", func(t *testing.T) {
|
|
p := New("foo.api", `@server()`)
|
|
result := p.ParseForUintTest()
|
|
assert.True(t, p.hasNoErrors())
|
|
stmt := result.Stmts[0]
|
|
atServerStmt, ok := stmt.(*ast.AtServerStmt)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, token.AT_SERVER, atServerStmt.AtServer.Token.Type)
|
|
assert.Equal(t, token.LPAREN, atServerStmt.LParen.Token.Type)
|
|
assert.Equal(t, token.RPAREN, atServerStmt.RParen.Token.Type)
|
|
assert.Equal(t, 0, len(atServerStmt.Values))
|
|
})
|
|
|
|
t.Run("invalidInSkipCommentMode", func(t *testing.T) {
|
|
var testData = []string{
|
|
`@server`,
|
|
`@server{`,
|
|
`@server(`,
|
|
`@server(}`,
|
|
`@server( //foo`,
|
|
`@server( foo`,
|
|
`@server( foo:`,
|
|
`@server( foo:bar bar`,
|
|
`@server( foo:bar bar,`,
|
|
`@server( foo:bar bar: 123`,
|
|
`@server( foo:bar bar: ""`,
|
|
`@server( foo:bar bar: @`,
|
|
`@server("":foo)`,
|
|
`@server(foo:bar,baz)`,
|
|
`@server(foo:/`,
|
|
`@server(foo:/v`,
|
|
`@server(foo:/v1/`,
|
|
`@server(foo:/v1/v`,
|
|
`@server(foo:/v1/v2`,
|
|
`@server(foo: m1,`,
|
|
`@server(foo: m1,)`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.Parse()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
|
|
t.Run("invalidWithNoSkipCommentMode", func(t *testing.T) {
|
|
var testData = []string{
|
|
`@server`,
|
|
`@server //foo`,
|
|
`@server /*foo*/`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.Parse()
|
|
assertx.Error(t, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/athandler_test.api
|
|
var atHandlerTestAPI string
|
|
|
|
func TestParser_Parse_atHandler(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`@handler foo`,
|
|
`@handler foo1`,
|
|
`@handler _bar`,
|
|
}
|
|
|
|
p := New("foo.api", atHandlerTestAPI)
|
|
result := p.ParseForUintTest()
|
|
assert.True(t, p.hasNoErrors())
|
|
for idx, v := range testData {
|
|
stmt := result.Stmts[idx]
|
|
atHandlerStmt, ok := stmt.(*ast.AtHandlerStmt)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, v, atHandlerStmt.Format(""))
|
|
}
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`@handler`,
|
|
`@handler 1`,
|
|
`@handler ""`,
|
|
`@handler @`,
|
|
`@handler $`,
|
|
`@handler ()`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.ParseForUintTest()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/atdoc_literal_test.api
|
|
var atDocLiteralTestAPI string
|
|
|
|
func TestParser_Parse_atDocLiteral(t *testing.T) {
|
|
t.Run("validLiteral", func(t *testing.T) {
|
|
var testData = []string{
|
|
`""`,
|
|
`"foo"`,
|
|
`"bar"`,
|
|
}
|
|
|
|
p := New("foo.api", atDocLiteralTestAPI)
|
|
result := p.ParseForUintTest()
|
|
assert.True(t, p.hasNoErrors())
|
|
for idx, v := range testData {
|
|
stmt := result.Stmts[idx]
|
|
atDocLitStmt, ok := stmt.(*ast.AtDocLiteralStmt)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, v, atDocLitStmt.Value.Token.Text)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`@doc`,
|
|
`@doc "`,
|
|
`@doc $`,
|
|
`@doc 好`,
|
|
`@doc |`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.ParseForUintTest()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/atdoc_group_test.api
|
|
var atDocGroupTestAPI string
|
|
|
|
func TestParser_Parse_atDocGroup(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
var testData = `@doc (
|
|
foo: "foo"
|
|
bar: "bar"
|
|
baz: ""
|
|
)`
|
|
|
|
p := New("foo.api", atDocGroupTestAPI)
|
|
result := p.ParseForUintTest()
|
|
assert.True(t, p.hasNoErrors())
|
|
stmt := result.Stmts[0]
|
|
atDocGroupStmt, _ := stmt.(*ast.AtDocGroupStmt)
|
|
assert.Equal(t, testData, atDocGroupStmt.Format(""))
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`@doc{`,
|
|
`@doc(`,
|
|
`@doc(}`,
|
|
`@doc( foo`,
|
|
`@doc( foo:`,
|
|
`@doc( foo: 123`,
|
|
`@doc( foo: )`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.ParseForUintTest()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
//go:embed testdata/service_test.api
|
|
var serviceTestAPI string
|
|
|
|
func TestParser_Parse_service(t *testing.T) {
|
|
assertEqual := func(t *testing.T, expected, actual *ast.ServiceStmt) {
|
|
if expected.AtServerStmt == nil {
|
|
assert.Nil(t, actual.AtServerStmt)
|
|
}
|
|
assert.Equal(t, expected.Service.Token.Type, actual.Service.Token.Type)
|
|
assert.Equal(t, expected.Service.Token.Text, actual.Service.Token.Text)
|
|
assert.Equal(t, expected.Name.Format(""), actual.Name.Format(""))
|
|
assert.Equal(t, expected.LBrace.Token.Type, actual.LBrace.Token.Type)
|
|
assert.Equal(t, expected.RBrace.Token.Text, actual.RBrace.Token.Text)
|
|
assert.Equal(t, len(expected.Routes), len(actual.Routes))
|
|
for idx, v := range expected.Routes {
|
|
actualItem := actual.Routes[idx]
|
|
if v.AtDoc == nil {
|
|
assert.Nil(t, actualItem.AtDoc)
|
|
} else {
|
|
assert.Equal(t, v.AtDoc.Format(""), actualItem.AtDoc.Format(""))
|
|
}
|
|
assert.Equal(t, v.AtHandler.Format(""), actualItem.AtHandler.Format(""))
|
|
assert.Equal(t, v.Route.Format(""), actualItem.Route.Format(""))
|
|
}
|
|
}
|
|
|
|
t.Run("valid", func(t *testing.T) {
|
|
var testData = []*ast.ServiceStmt{
|
|
{
|
|
Service: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "service"}),
|
|
Name: &ast.ServiceNameExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "foo"}),
|
|
},
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
Routes: []*ast.ServiceItemStmt{
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "bar"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}),
|
|
Path: &ast.PathExpr{Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/ping",
|
|
})},
|
|
},
|
|
},
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "bar"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}),
|
|
Path: &ast.PathExpr{Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/ping",
|
|
})},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Service: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "service"}),
|
|
Name: &ast.ServiceNameExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "bar"}),
|
|
},
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
Routes: []*ast.ServiceItemStmt{
|
|
{
|
|
AtDoc: &ast.AtDocLiteralStmt{
|
|
AtDoc: ast.NewTokenNode(token.Token{Type: token.AT_DOC, Text: "@doc"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.STRING, Text: `"bar"`}),
|
|
},
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "foo"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo/:bar",
|
|
}),
|
|
},
|
|
Request: &ast.BodyStmt{
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
Body: &ast.BodyExpr{
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
AtDoc: &ast.AtDocLiteralStmt{
|
|
AtDoc: ast.NewTokenNode(token.Token{Type: token.AT_DOC, Text: "@doc"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.STRING, Text: `"bar"`}),
|
|
},
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "foo"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo/:bar",
|
|
}),
|
|
},
|
|
Request: &ast.BodyStmt{
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
Body: &ast.BodyExpr{
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
},
|
|
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: "("}),
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "foo"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo/:bar",
|
|
}),
|
|
},
|
|
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"}),
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "foo"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "get"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo/:bar",
|
|
}),
|
|
},
|
|
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"}),
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Service: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "service"}),
|
|
Name: &ast.ServiceNameExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "baz-api"}),
|
|
},
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
Routes: []*ast.ServiceItemStmt{
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "foo"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "post"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo/:bar/foo-bar-baz",
|
|
}),
|
|
},
|
|
Request: &ast.BodyStmt{
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
Body: &ast.BodyExpr{
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
},
|
|
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{
|
|
Star: ast.NewTokenNode(token.Token{Type: token.MUL, Text: "*"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Bar"}),
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "foo"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "post"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo/:bar/foo-bar-baz",
|
|
}),
|
|
},
|
|
Request: &ast.BodyStmt{
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
Body: &ast.BodyExpr{
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
},
|
|
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{
|
|
Star: ast.NewTokenNode(token.Token{Type: token.MUL, Text: "*"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Bar"}),
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "bar"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "post"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo",
|
|
}),
|
|
},
|
|
Request: &ast.BodyStmt{
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
Body: &ast.BodyExpr{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
},
|
|
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{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
Star: ast.NewTokenNode(token.Token{Type: token.MUL, Text: "*"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Bar"}),
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
AtHandler: &ast.AtHandlerStmt{
|
|
AtHandler: ast.NewTokenNode(token.Token{Type: token.AT_HANDLER, Text: "@handler"}),
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "bar"}),
|
|
},
|
|
Route: &ast.RouteStmt{
|
|
Method: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "post"}),
|
|
Path: &ast.PathExpr{
|
|
Value: ast.NewTokenNode(token.Token{
|
|
Type: token.PATH,
|
|
Text: "/foo",
|
|
}),
|
|
},
|
|
Request: &ast.BodyStmt{
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
Body: &ast.BodyExpr{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
},
|
|
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{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
Star: ast.NewTokenNode(token.Token{Type: token.MUL, Text: "*"}),
|
|
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Bar"}),
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
p := New("foo.api", serviceTestAPI)
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
for idx, v := range testData {
|
|
stmt := result.Stmts[idx]
|
|
serviceStmt, ok := stmt.(*ast.ServiceStmt)
|
|
assert.True(t, ok)
|
|
assertEqual(t, v, serviceStmt)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
`service`,
|
|
`service foo`,
|
|
`service -`,
|
|
`service foo-`,
|
|
`service foo-api`,
|
|
`service foo(`,
|
|
`service foo$`,
|
|
`service foo好`,
|
|
`service foo{`,
|
|
`service foo{ @doc`,
|
|
`service foo{ @doc $`,
|
|
`service foo{ @doc ""`,
|
|
`service foo{ @handler`,
|
|
`service foo{ @handler foo`,
|
|
`service foo{ @handler foo bar`,
|
|
`service foo{ @handler foo get`,
|
|
`service foo{ @handler foo get /`,
|
|
`service foo{ @handler foo get \`,
|
|
`service foo{ @handler foo get /:`,
|
|
`service foo{ @handler foo get /::`,
|
|
`service foo{ @handler foo get /:foo-`,
|
|
`service foo{ @handler foo get /:foo--`,
|
|
`service foo{ @handler foo get /:foo-bar/-`,
|
|
`service foo{ @handler foo get /:foo-bar/baz`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (`,
|
|
`service foo{ @handler foo get /:foo-bar/baz foo`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo)`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo) return`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo) returns `,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo) returns (`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo) returns {`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo) returns (bar`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo) returns (bar}`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (foo) returns (bar)`,
|
|
`service foo{ @handler foo get /:foo-bar/baz ([`,
|
|
`service foo{ @handler foo get /:foo-bar/baz ([@`,
|
|
`service foo{ @handler foo get /:foo-bar/baz ([]`,
|
|
`service foo{ @handler foo get /:foo-bar/baz ([]*`,
|
|
`service foo{ @handler foo get /:foo-bar/baz ([]*Foo`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (*`,
|
|
`service foo{ @handler foo get /:foo-bar/baz (*Foo`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns (`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns ([`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns ([]`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns ([]*`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns ([]*Foo`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns (*`,
|
|
`service foo{ @handler foo get /:foo-bar/baz returns (*Foo`,
|
|
`service foo{ @handler foo get /ping (Foo) returns (Bar)]`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
_ = p.Parse()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
|
|
t.Run("invalidBeginWithAtServer", func(t *testing.T) {
|
|
var testData = []string{
|
|
`@server(`,
|
|
`@server() service`,
|
|
`@server() foo`,
|
|
`@server() service fo`,
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
p.init()
|
|
_ = p.parseService()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParser_Parse_pathItem(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
var testData = []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{input: "foo", expected: "foo"},
|
|
{input: "foo2", expected: "foo2"},
|
|
{input: "foo-bar", expected: "foo-bar"},
|
|
{input: "foo-bar2", expected: "foo-bar2"},
|
|
{input: "foo-bar-baz", expected: "foo-bar-baz"},
|
|
{input: "_foo-bar-baz", expected: "_foo-bar-baz"},
|
|
{input: "_foo_bar-baz", expected: "_foo_bar-baz"},
|
|
{input: "_foo_bar_baz", expected: "_foo_bar_baz"},
|
|
{input: "_foo_bar_baz", expected: "_foo_bar_baz"},
|
|
{input: "foo/", expected: "foo"},
|
|
{input: "foo(", expected: "foo"},
|
|
{input: "foo returns", expected: "foo"},
|
|
{input: "foo @doc", expected: "foo"},
|
|
{input: "foo @handler", expected: "foo"},
|
|
{input: "foo }", expected: "foo"},
|
|
{input: "1", expected: "1"},
|
|
{input: "11", expected: "11"},
|
|
}
|
|
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)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
var testData = []string{
|
|
"-foo",
|
|
"foo-",
|
|
"foo-2",
|
|
"2-2",
|
|
"foo-bar-123",
|
|
"foo-bar-$",
|
|
"foo-bar-好",
|
|
"foo-bar@",
|
|
"foo-barの",
|
|
}
|
|
for _, v := range testData {
|
|
p := New("foo.api", v)
|
|
ok := p.nextToken()
|
|
assert.True(t, ok)
|
|
p.parsePathItem()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParser_Parse_parseTypeStmt(t *testing.T) {
|
|
assertEqual := func(t *testing.T, expected, actual ast.Stmt) {
|
|
if expected == nil {
|
|
assert.Nil(t, actual)
|
|
return
|
|
}
|
|
assert.Equal(t, expected.Format(""), actual.Format(""))
|
|
}
|
|
t.Run("parseTypeLiteralStmt", func(t *testing.T) {
|
|
var testData = []struct {
|
|
input string
|
|
expected ast.TypeStmt
|
|
}{
|
|
{
|
|
input: "type Int int",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Int"}),
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Int interface{}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Int"}),
|
|
DataType: &ast.InterfaceDataType{
|
|
Interface: ast.NewTokenNode(token.Token{Type: token.ANY, Text: "interface{}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Int any",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Int"}),
|
|
DataType: &ast.AnyDataType{
|
|
Any: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "any"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Int = int",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Int"}),
|
|
Assign: ast.NewTokenNode(token.Token{Type: token.ASSIGN, Text: "="}),
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Array [2]int",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Array"}),
|
|
DataType: &ast.ArrayDataType{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
Length: ast.NewTokenNode(token.Token{Type: token.INT, Text: "2"}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Array [...]int",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Array"}),
|
|
DataType: &ast.ArrayDataType{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
Length: ast.NewTokenNode(token.Token{Type: token.ELLIPSIS, Text: "..."}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Map map[string]int",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Map"}),
|
|
DataType: &ast.MapDataType{
|
|
Map: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "map"}),
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
Key: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "string"})},
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
Value: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Pointer *int",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Pointer"}),
|
|
DataType: &ast.PointerDataType{
|
|
Star: ast.NewTokenNode(token.Token{Type: token.MUL, Text: "*"}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Slice []int",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Slice"}),
|
|
DataType: &ast.SliceDataType{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Foo {}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Foo {Bar\n*Baz}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
Elements: ast.ElemExprList{
|
|
{
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Bar"}),
|
|
},
|
|
},
|
|
{
|
|
DataType: &ast.PointerDataType{
|
|
Star: ast.NewTokenNode(token.Token{Type: token.MUL, Text: "*"}),
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Baz"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Foo {Bar `json:\"bar\"`\n*Baz `json:\"baz\"`}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
Elements: ast.ElemExprList{
|
|
{
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Bar"}),
|
|
},
|
|
Tag: ast.NewTokenNode(token.Token{
|
|
Type: token.RAW_STRING,
|
|
Text: "`json:\"bar\"`",
|
|
}),
|
|
},
|
|
{
|
|
DataType: &ast.PointerDataType{
|
|
Star: ast.NewTokenNode(token.Token{Type: token.MUL, Text: "*"}),
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Baz"}),
|
|
},
|
|
},
|
|
Tag: ast.NewTokenNode(token.Token{
|
|
Type: token.RAW_STRING,
|
|
Text: "`json:\"baz\"`",
|
|
}),
|
|
},
|
|
},
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Foo {Name string}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
Elements: ast.ElemExprList{
|
|
{
|
|
Name: []*ast.TokenNode{ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Name"})},
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "string"}),
|
|
},
|
|
},
|
|
},
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Foo {Name,Desc string}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
Elements: ast.ElemExprList{
|
|
{
|
|
Name: []*ast.TokenNode{
|
|
{Token: token.Token{Type: token.IDENT, Text: "Name"}},
|
|
{Token: token.Token{Type: token.IDENT, Text: "Desc"}},
|
|
},
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "string"}),
|
|
},
|
|
},
|
|
},
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Foo {Name string\n Age int `json:\"age\"`}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
Elements: ast.ElemExprList{
|
|
{
|
|
Name: []*ast.TokenNode{
|
|
{Token: token.Token{Type: token.IDENT, Text: "Name"}},
|
|
},
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "string"}),
|
|
},
|
|
},
|
|
{
|
|
Name: []*ast.TokenNode{
|
|
{Token: token.Token{Type: token.IDENT, Text: "Age"}},
|
|
},
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"}),
|
|
},
|
|
Tag: ast.NewTokenNode(token.Token{Type: token.RAW_STRING, Text: "`json:\"age\"`"}),
|
|
},
|
|
},
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
input: "type Foo {Bar {Name string}}",
|
|
expected: &ast.TypeLiteralStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
Expr: &ast.TypeExpr{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
Elements: ast.ElemExprList{
|
|
{
|
|
Name: []*ast.TokenNode{
|
|
{Token: token.Token{Type: token.IDENT, Text: "Bar"}},
|
|
},
|
|
DataType: &ast.StructDataType{
|
|
LBrace: ast.NewTokenNode(token.Token{Type: token.LBRACE, Text: "{"}),
|
|
Elements: ast.ElemExprList{
|
|
{
|
|
Name: []*ast.TokenNode{
|
|
{Token: token.Token{Type: token.IDENT, Text: "Name"}},
|
|
},
|
|
DataType: &ast.BaseDataType{
|
|
Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "string"}),
|
|
},
|
|
},
|
|
},
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
RBrace: ast.NewTokenNode(token.Token{Type: token.RBRACE, Text: "}"}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, val := range testData {
|
|
p := New("test.api", val.input)
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
assert.Equal(t, 1, len(result.Stmts))
|
|
one := result.Stmts[0]
|
|
assertEqual(t, val.expected, one)
|
|
}
|
|
})
|
|
t.Run("parseTypeGroupStmt", func(t *testing.T) {
|
|
var testData = []struct {
|
|
input string
|
|
expected ast.TypeStmt
|
|
}{
|
|
{
|
|
input: "type (Int int)",
|
|
expected: &ast.TypeGroupStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
ExprList: []*ast.TypeExpr{
|
|
{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Int"}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
{
|
|
input: "type (Int = int)",
|
|
expected: &ast.TypeGroupStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
ExprList: []*ast.TypeExpr{
|
|
{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Int"}),
|
|
Assign: ast.NewTokenNode(token.Token{Type: token.ASSIGN, Text: "="}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
{
|
|
input: "type (Array [2]int)",
|
|
expected: &ast.TypeGroupStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
ExprList: []*ast.TypeExpr{
|
|
{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Array"}),
|
|
DataType: &ast.ArrayDataType{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
Length: ast.NewTokenNode(token.Token{Type: token.INT, Text: "2"}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
{
|
|
input: "type (Array [...]int)",
|
|
expected: &ast.TypeGroupStmt{
|
|
Type: ast.NewTokenNode(token.Token{Type: token.TYPE, Text: "type"}),
|
|
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
|
|
ExprList: []*ast.TypeExpr{
|
|
{
|
|
Name: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Array"}),
|
|
DataType: &ast.ArrayDataType{
|
|
LBrack: ast.NewTokenNode(token.Token{Type: token.LBRACK, Text: "["}),
|
|
Length: ast.NewTokenNode(token.Token{Type: token.ELLIPSIS, Text: "..."}),
|
|
RBrack: ast.NewTokenNode(token.Token{Type: token.RBRACK, Text: "]"}),
|
|
DataType: &ast.BaseDataType{Base: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "int"})},
|
|
},
|
|
},
|
|
},
|
|
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
|
|
},
|
|
},
|
|
}
|
|
for _, val := range testData {
|
|
p := New("test.api", val.input)
|
|
result := p.Parse()
|
|
assert.True(t, p.hasNoErrors())
|
|
assert.Equal(t, 1, len(result.Stmts))
|
|
one := result.Stmts[0]
|
|
assertEqual(t, val.expected, one)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParser_Parse_parseTypeStmt_invalid(t *testing.T) {
|
|
var testData = []string{
|
|
/**************** type literal stmt ****************/
|
|
"type",
|
|
"type @",
|
|
"type Foo",
|
|
"type Foo = ",
|
|
"type Foo = [",
|
|
"type Foo = []",
|
|
"type Foo = [2",
|
|
"type Foo = [2]",
|
|
"type Foo = [...",
|
|
"type Foo = [...]",
|
|
"type Foo map",
|
|
"type Foo map[",
|
|
"type Foo map[]",
|
|
"type Foo map[string",
|
|
"type Foo map[123",
|
|
"type Foo map[string]",
|
|
"type Foo map[string]@",
|
|
"type Foo interface",
|
|
"type Foo interface{",
|
|
"type Foo *",
|
|
"type Foo *123",
|
|
"type Foo *go",
|
|
"type Foo {",
|
|
"type Foo { Foo ",
|
|
"type Foo { Foo int",
|
|
"type Foo { Foo int `",
|
|
"type Foo { Foo int ``",
|
|
"type Foo { Foo,",
|
|
"type Foo { Foo@",
|
|
"type Foo { Foo,Bar",
|
|
"type Foo { Foo,Bar int",
|
|
"type Foo { Foo,Bar int `baz`",
|
|
"type Foo { Foo,Bar int `baz`)",
|
|
"type Foo { Foo,Bar int `baz`@",
|
|
"type Foo *",
|
|
"type Foo *{",
|
|
"type Foo *[",
|
|
"type Foo *[]",
|
|
"type Foo *map",
|
|
"type Foo *map[",
|
|
"type Foo *map[int",
|
|
"type Foo *map[int]123",
|
|
"type Foo *map[int]@",
|
|
"type Foo *好",
|
|
|
|
/**************** type group stmt ****************/
|
|
"type (@",
|
|
"type (Foo",
|
|
"type (Foo = ",
|
|
"type (Foo = [",
|
|
"type (Foo = []",
|
|
"type (Foo = [2",
|
|
"type (Foo = [2]",
|
|
"type (Foo = [...",
|
|
"type (Foo = [...]",
|
|
"type (Foo map",
|
|
"type (Foo map[",
|
|
"type (Foo map[]",
|
|
"type (Foo map[string",
|
|
"type (Foo map[123",
|
|
"type (Foo map[string]",
|
|
"type (Foo map[string]@",
|
|
"type (Foo interface",
|
|
"type (Foo interface{",
|
|
"type (Foo *",
|
|
"type (Foo *123",
|
|
"type (Foo *go",
|
|
"type (Foo {",
|
|
"type (Foo { Foo ",
|
|
"type (Foo { Foo int",
|
|
"type (Foo { Foo int `",
|
|
"type (Foo { Foo int ``",
|
|
"type (Foo { Foo,",
|
|
"type (Foo { Foo@",
|
|
"type (Foo { Foo,Bar",
|
|
"type (Foo { Foo,Bar int",
|
|
"type (Foo { Foo,Bar int `baz`",
|
|
"type (Foo { Foo,Bar int `baz`)",
|
|
"type (Foo { Foo,Bar int `baz`@",
|
|
"type (Foo *",
|
|
"type (Foo *{",
|
|
"type (Foo *[",
|
|
"type (Foo *[]",
|
|
"type (Foo *map",
|
|
"type (Foo *map[",
|
|
"type (Foo *map[int",
|
|
"type (Foo *map[int]123",
|
|
"type (Foo *map[int]@",
|
|
"type (Foo *好",
|
|
"type (Foo)",
|
|
"type (Foo int\nBar)",
|
|
"type (Foo int\nBar string `)",
|
|
"type (())",
|
|
"type go int",
|
|
"type A func",
|
|
"type A {map[string]int}",
|
|
"type A {Name \n string}",
|
|
}
|
|
|
|
for _, v := range testData {
|
|
p := New("test.api", v)
|
|
p.Parse()
|
|
assertx.ErrorOrigin(t, v, p.errors...)
|
|
}
|
|
}
|