feat(goctl): Add api parser (#2585)
This commit is contained in:
412
tools/goctl/pkg/parser/api/parser/analyzer.go
Normal file
412
tools/goctl/pkg/parser/api/parser/analyzer.go
Normal file
@@ -0,0 +1,412 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||
)
|
||||
|
||||
// Analyzer analyzes the ast and converts it to spec.
|
||||
type Analyzer struct {
|
||||
api *API
|
||||
spec *spec.ApiSpec
|
||||
}
|
||||
|
||||
func (a *Analyzer) astTypeToSpec(in ast.DataType) (spec.Type, error) {
|
||||
isLiteralType := func(dt ast.DataType) bool {
|
||||
_, ok := dt.(*ast.BaseDataType)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
_, ok = dt.(*ast.AnyDataType)
|
||||
return ok
|
||||
}
|
||||
switch v := (in).(type) {
|
||||
case *ast.BaseDataType:
|
||||
raw := v.RawText()
|
||||
if IsBaseType(raw) {
|
||||
return spec.PrimitiveType{
|
||||
RawName: raw,
|
||||
}, nil
|
||||
}
|
||||
return spec.DefineStruct{RawName: raw}, nil
|
||||
case *ast.AnyDataType:
|
||||
return nil, ast.SyntaxError(v.Pos(), "unsupported any type")
|
||||
case *ast.StructDataType:
|
||||
// TODO(keson) feature: can be extended
|
||||
case *ast.InterfaceDataType:
|
||||
return spec.InterfaceType{RawName: v.RawText()}, nil
|
||||
case *ast.MapDataType:
|
||||
if !isLiteralType(v.Key) {
|
||||
return nil, ast.SyntaxError(v.Pos(), "expected literal type, got <%T>", v)
|
||||
}
|
||||
if !v.Key.CanEqual() {
|
||||
return nil, ast.SyntaxError(v.Pos(), "map key <%T> must be equal data type", v)
|
||||
}
|
||||
value, err := a.astTypeToSpec(v.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spec.MapType{
|
||||
RawName: v.RawText(),
|
||||
Key: v.RawText(),
|
||||
Value: value,
|
||||
}, nil
|
||||
case *ast.PointerDataType:
|
||||
raw := v.DataType.RawText()
|
||||
if IsBaseType(raw) {
|
||||
return spec.PointerType{RawName: v.RawText(), Type: spec.PrimitiveType{RawName: raw}}, nil
|
||||
}
|
||||
|
||||
value, err := a.astTypeToSpec(v.DataType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spec.PointerType{
|
||||
RawName: v.RawText(),
|
||||
Type: value,
|
||||
}, nil
|
||||
case *ast.ArrayDataType:
|
||||
if v.Length.Token.Type == token.ELLIPSIS {
|
||||
return nil, ast.SyntaxError(v.Pos(), "Array: unsupported dynamic length")
|
||||
}
|
||||
value, err := a.astTypeToSpec(v.DataType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spec.ArrayType{
|
||||
RawName: v.RawText(),
|
||||
Value: value,
|
||||
}, nil
|
||||
case *ast.SliceDataType:
|
||||
value, err := a.astTypeToSpec(v.DataType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spec.ArrayType{
|
||||
RawName: v.RawText(),
|
||||
Value: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, ast.SyntaxError(in.Pos(), "unsupported type <%T>", in)
|
||||
}
|
||||
|
||||
func (a *Analyzer) convert2Spec() error {
|
||||
if err := a.fillTypes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.fillService()
|
||||
}
|
||||
|
||||
func (a *Analyzer) convertAtDoc(atDoc ast.AtDocStmt) spec.AtDoc {
|
||||
var ret spec.AtDoc
|
||||
switch val := atDoc.(type) {
|
||||
case *ast.AtDocLiteralStmt:
|
||||
ret.Text = val.Value.Token.Text
|
||||
case *ast.AtDocGroupStmt:
|
||||
ret.Properties = a.convertKV(val.Values)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *Analyzer) convertKV(kv []*ast.KVExpr) map[string]string {
|
||||
var ret = map[string]string{}
|
||||
for _, v := range kv {
|
||||
key := strings.TrimSuffix(v.Key.Token.Text, ":")
|
||||
ret[key] = v.Value.Token.Text
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *Analyzer) fieldToMember(field *ast.ElemExpr) (spec.Member, error) {
|
||||
var name []string
|
||||
for _, v := range field.Name {
|
||||
name = append(name, v.Token.Text)
|
||||
}
|
||||
|
||||
tp, err := a.astTypeToSpec(field.DataType)
|
||||
if err != nil {
|
||||
return spec.Member{}, err
|
||||
}
|
||||
|
||||
head, leading := field.CommentGroup()
|
||||
m := spec.Member{
|
||||
Name: strings.Join(name, ", "),
|
||||
Type: tp,
|
||||
Docs: head.List(),
|
||||
Comment: leading.String(),
|
||||
IsInline: field.IsAnonymous(),
|
||||
}
|
||||
if field.Tag != nil {
|
||||
m.Tag = field.Tag.Token.Text
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (a *Analyzer) fillRouteType(route *spec.Route) error {
|
||||
if route.RequestType != nil {
|
||||
switch route.RequestType.(type) {
|
||||
case spec.DefineStruct:
|
||||
tp, err := a.findDefinedType(route.RequestType.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route.RequestType = tp
|
||||
}
|
||||
}
|
||||
|
||||
if route.ResponseType != nil {
|
||||
switch route.ResponseType.(type) {
|
||||
case spec.DefineStruct:
|
||||
tp, err := a.findDefinedType(route.ResponseType.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route.ResponseType = tp
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Analyzer) fillService() error {
|
||||
var groups []spec.Group
|
||||
for _, item := range a.api.ServiceStmts {
|
||||
var group spec.Group
|
||||
if item.AtServerStmt != nil {
|
||||
group.Annotation.Properties = a.convertKV(item.AtServerStmt.Values)
|
||||
}
|
||||
|
||||
for _, astRoute := range item.Routes {
|
||||
head, leading := astRoute.CommentGroup()
|
||||
route := spec.Route{
|
||||
Method: astRoute.Route.Method.Token.Text,
|
||||
Path: astRoute.Route.Path.Format(""),
|
||||
Doc: head.List(),
|
||||
Comment: leading.List(),
|
||||
}
|
||||
if astRoute.AtDoc != nil {
|
||||
route.AtDoc = a.convertAtDoc(astRoute.AtDoc)
|
||||
}
|
||||
if astRoute.AtHandler != nil {
|
||||
route.AtDoc = a.convertAtDoc(astRoute.AtDoc)
|
||||
route.Handler = astRoute.AtHandler.Name.Token.Text
|
||||
head, leading := astRoute.AtHandler.CommentGroup()
|
||||
route.HandlerDoc = head.List()
|
||||
route.HandlerComment = leading.List()
|
||||
}
|
||||
|
||||
if astRoute.Route.Request != nil && astRoute.Route.Request.Body != nil {
|
||||
requestType, err := a.getType(astRoute.Route.Request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.RequestType = requestType
|
||||
}
|
||||
if astRoute.Route.Response != nil && astRoute.Route.Response.Body != nil {
|
||||
responseType, err := a.getType(astRoute.Route.Response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route.ResponseType = responseType
|
||||
}
|
||||
|
||||
if err := a.fillRouteType(&route); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group.Routes = append(group.Routes, route)
|
||||
|
||||
name := item.Name.Format("")
|
||||
if len(a.spec.Service.Name) > 0 && a.spec.Service.Name != name {
|
||||
return ast.SyntaxError(item.Name.Pos(), "multiple service names defined <%s> and <%s>", name, a.spec.Service.Name)
|
||||
}
|
||||
a.spec.Service.Name = name
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
|
||||
a.spec.Service.Groups = groups
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Analyzer) fillTypes() error {
|
||||
for _, item := range a.api.TypeStmt {
|
||||
switch v := (item).(type) {
|
||||
case *ast.TypeLiteralStmt:
|
||||
err := a.fillTypeExpr(v.Expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case *ast.TypeGroupStmt:
|
||||
for _, expr := range v.ExprList {
|
||||
err := a.fillTypeExpr(expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var types []spec.Type
|
||||
for _, item := range a.spec.Types {
|
||||
switch v := (item).(type) {
|
||||
case spec.DefineStruct:
|
||||
var members []spec.Member
|
||||
for _, member := range v.Members {
|
||||
switch v := member.Type.(type) {
|
||||
case spec.DefineStruct:
|
||||
tp, err := a.findDefinedType(v.RawName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
member.Type = tp
|
||||
}
|
||||
members = append(members, member)
|
||||
}
|
||||
v.Members = members
|
||||
types = append(types, v)
|
||||
default:
|
||||
return fmt.Errorf("unknown type %+v", v)
|
||||
}
|
||||
}
|
||||
a.spec.Types = types
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Analyzer) fillTypeExpr(expr *ast.TypeExpr) error {
|
||||
head, _ := expr.CommentGroup()
|
||||
switch val := expr.DataType.(type) {
|
||||
case *ast.StructDataType:
|
||||
var members []spec.Member
|
||||
for _, item := range val.Elements {
|
||||
m, err := a.fieldToMember(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
members = append(members, m)
|
||||
}
|
||||
a.spec.Types = append(a.spec.Types, spec.DefineStruct{
|
||||
RawName: expr.Name.Token.Text,
|
||||
Members: members,
|
||||
Docs: head.List(),
|
||||
})
|
||||
return nil
|
||||
default:
|
||||
return ast.SyntaxError(expr.Pos(), "expected <struct> expr, got <%T>", expr.DataType)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Analyzer) findDefinedType(name string) (spec.Type, error) {
|
||||
for _, item := range a.spec.Types {
|
||||
if _, ok := item.(spec.DefineStruct); ok {
|
||||
if item.Name() == name {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("type %s not defined", name)
|
||||
}
|
||||
|
||||
func (a *Analyzer) getType(expr *ast.BodyStmt) (spec.Type, error) {
|
||||
body := expr.Body
|
||||
var tp spec.Type
|
||||
var err error
|
||||
var rawText = body.Format("")
|
||||
if IsBaseType(body.Value.Token.Text) {
|
||||
tp = spec.PrimitiveType{RawName: body.Value.Token.Text}
|
||||
} else {
|
||||
tp, err = a.findDefinedType(body.Value.Token.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if body.LBrack != nil {
|
||||
if body.Star != nil {
|
||||
return spec.PointerType{
|
||||
RawName: rawText,
|
||||
Type: tp,
|
||||
}, nil
|
||||
}
|
||||
return spec.ArrayType{
|
||||
RawName: rawText,
|
||||
Value: tp,
|
||||
}, nil
|
||||
}
|
||||
if body.Star != nil {
|
||||
return spec.PointerType{
|
||||
RawName: rawText,
|
||||
Type: tp,
|
||||
}, nil
|
||||
}
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
// Parse parses the given file and returns the parsed spec.
|
||||
func Parse(filename string, src interface{}) (*spec.ApiSpec, error) {
|
||||
p := New(filename, src)
|
||||
ast := p.Parse()
|
||||
if err := p.CheckErrors(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var importManager = make(map[string]placeholder.Type)
|
||||
importManager[ast.Filename] = placeholder.PlaceHolder
|
||||
api, err := convert2API(ast, importManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result = new(spec.ApiSpec)
|
||||
analyzer := Analyzer{
|
||||
api: api,
|
||||
spec: result,
|
||||
}
|
||||
|
||||
err = analyzer.convert2Spec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var kind = map[string]placeholder.Type{
|
||||
"bool": placeholder.PlaceHolder,
|
||||
"int": placeholder.PlaceHolder,
|
||||
"int8": placeholder.PlaceHolder,
|
||||
"int16": placeholder.PlaceHolder,
|
||||
"int32": placeholder.PlaceHolder,
|
||||
"int64": placeholder.PlaceHolder,
|
||||
"uint": placeholder.PlaceHolder,
|
||||
"uint8": placeholder.PlaceHolder,
|
||||
"uint16": placeholder.PlaceHolder,
|
||||
"uint32": placeholder.PlaceHolder,
|
||||
"uint64": placeholder.PlaceHolder,
|
||||
"uintptr": placeholder.PlaceHolder,
|
||||
"float32": placeholder.PlaceHolder,
|
||||
"float64": placeholder.PlaceHolder,
|
||||
"complex64": placeholder.PlaceHolder,
|
||||
"complex128": placeholder.PlaceHolder,
|
||||
"string": placeholder.PlaceHolder,
|
||||
"byte": placeholder.PlaceHolder,
|
||||
"rune": placeholder.PlaceHolder,
|
||||
"any": placeholder.PlaceHolder,
|
||||
}
|
||||
|
||||
// IsBaseType returns true if the given type is a base type.
|
||||
func IsBaseType(text string) bool {
|
||||
_, ok := kind[text]
|
||||
return ok
|
||||
}
|
||||
45
tools/goctl/pkg/parser/api/parser/analyzer_test.go
Normal file
45
tools/goctl/pkg/parser/api/parser/analyzer_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/assertx"
|
||||
)
|
||||
|
||||
func Test_Parse(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
_, err := Parse("./testdata/example.api", nil)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("./testdata/invalid.api")
|
||||
assert.NoError(t, err)
|
||||
splits := bytes.Split(data, []byte("-----"))
|
||||
var testFile []string
|
||||
for idx, split := range splits {
|
||||
replacer := strings.NewReplacer(" ", "", "\t", "", "\n", "", "\r", "", "\f", "")
|
||||
r := replacer.Replace(string(split))
|
||||
if len(r) == 0 {
|
||||
continue
|
||||
}
|
||||
filename := filepath.Join(t.TempDir(), fmt.Sprintf("invalid%d.api", idx))
|
||||
err := ioutil.WriteFile(filename, split, 0666)
|
||||
assert.NoError(t, err)
|
||||
testFile = append(testFile, filename)
|
||||
}
|
||||
for _, v := range testFile {
|
||||
_, err := Parse(v, nil)
|
||||
assertx.Error(t, err)
|
||||
}
|
||||
})
|
||||
t.Run("circleImport", func(t *testing.T) {
|
||||
_, err := Parse("./testdata/base.api", nil)
|
||||
assertx.Error(t, err)
|
||||
})
|
||||
}
|
||||
311
tools/goctl/pkg/parser/api/parser/api.go
Normal file
311
tools/goctl/pkg/parser/api/parser/api.go
Normal file
@@ -0,0 +1,311 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||
)
|
||||
|
||||
// API is the parsed api file.
|
||||
type API struct {
|
||||
Filename string
|
||||
Syntax *ast.SyntaxStmt
|
||||
info *ast.InfoStmt // Info block does not participate in code generation.
|
||||
importStmt []ast.ImportStmt // ImportStmt block does not participate in code generation.
|
||||
TypeStmt []ast.TypeStmt
|
||||
ServiceStmts []*ast.ServiceStmt
|
||||
importManager map[string]placeholder.Type
|
||||
}
|
||||
|
||||
func convert2API(a *ast.AST, importManager map[string]placeholder.Type) (*API, error) {
|
||||
var api = new(API)
|
||||
api.importManager = make(map[string]placeholder.Type)
|
||||
api.Filename = a.Filename
|
||||
for k, v := range importManager {
|
||||
api.importManager[k] = v
|
||||
}
|
||||
one := a.Stmts[0]
|
||||
syntax, ok := one.(*ast.SyntaxStmt)
|
||||
if !ok {
|
||||
return nil, ast.SyntaxError(one.Pos(), "expected syntax statement, got <%T>", one)
|
||||
}
|
||||
api.Syntax = syntax
|
||||
|
||||
for i := 1; i < len(a.Stmts); i++ {
|
||||
one := a.Stmts[i]
|
||||
switch val := one.(type) {
|
||||
case *ast.SyntaxStmt:
|
||||
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate syntax statement")
|
||||
case *ast.InfoStmt:
|
||||
if api.info != nil {
|
||||
return nil, ast.DuplicateStmtError(val.Pos(), "duplicate info statement")
|
||||
}
|
||||
api.info = val
|
||||
case ast.ImportStmt:
|
||||
api.importStmt = append(api.importStmt, val)
|
||||
case ast.TypeStmt:
|
||||
api.TypeStmt = append(api.TypeStmt, val)
|
||||
case *ast.ServiceStmt:
|
||||
api.ServiceStmts = append(api.ServiceStmts, val)
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.SelfCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api, nil
|
||||
}
|
||||
|
||||
func (api *API) checkImportStmt() error {
|
||||
f := newFilter()
|
||||
b := f.addCheckItem("import value expression")
|
||||
for _, v := range api.importStmt {
|
||||
switch val := v.(type) {
|
||||
case *ast.ImportLiteralStmt:
|
||||
b.check(val.Value)
|
||||
case *ast.ImportGroupStmt:
|
||||
b.check(val.Values...)
|
||||
}
|
||||
}
|
||||
return f.error()
|
||||
}
|
||||
|
||||
func (api *API) checkInfoStmt() error {
|
||||
if api.info == nil {
|
||||
return nil
|
||||
}
|
||||
f := newFilter()
|
||||
b := f.addCheckItem("info key expression")
|
||||
for _, v := range api.info.Values {
|
||||
b.check(v.Key)
|
||||
}
|
||||
return f.error()
|
||||
}
|
||||
|
||||
func (api *API) checkServiceStmt() error {
|
||||
f := newFilter()
|
||||
serviceNameChecker := f.addCheckItem("service name expression")
|
||||
handlerChecker := f.addCheckItem("handler expression")
|
||||
pathChecker := f.addCheckItem("path expression")
|
||||
var serviceName = map[string]string{}
|
||||
for _, v := range api.ServiceStmts {
|
||||
name := strings.TrimSuffix(v.Name.Format(""), "-api")
|
||||
if sn, ok := serviceName[name]; ok {
|
||||
if sn != name {
|
||||
serviceNameChecker.errorManager.add(ast.SyntaxError(v.Name.Pos(), "multiple service name"))
|
||||
}
|
||||
} else {
|
||||
serviceName[name] = name
|
||||
}
|
||||
var group = api.getAtServerValue(v.AtServerStmt, "prefix")
|
||||
for _, item := range v.Routes {
|
||||
handlerChecker.check(item.AtHandler.Name)
|
||||
path := fmt.Sprintf("[%s]:%s", group, item.Route.Format(""))
|
||||
pathChecker.check(ast.NewTokenNode(token.Token{
|
||||
Text: path,
|
||||
Position: item.Route.Pos(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
return f.error()
|
||||
}
|
||||
|
||||
func (api *API) checkTypeStmt() error {
|
||||
f := newFilter()
|
||||
b := f.addCheckItem("type expression")
|
||||
for _, v := range api.TypeStmt {
|
||||
switch val := v.(type) {
|
||||
case *ast.TypeLiteralStmt:
|
||||
b.check(val.Expr.Name)
|
||||
case *ast.TypeGroupStmt:
|
||||
for _, expr := range val.ExprList {
|
||||
b.check(expr.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.error()
|
||||
}
|
||||
|
||||
func (api *API) checkTypeDeclareContext() error {
|
||||
var typeMap = map[string]placeholder.Type{}
|
||||
for _, v := range api.TypeStmt {
|
||||
switch tp := v.(type) {
|
||||
case *ast.TypeLiteralStmt:
|
||||
typeMap[tp.Expr.Name.Token.Text] = placeholder.PlaceHolder
|
||||
case *ast.TypeGroupStmt:
|
||||
for _, v := range tp.ExprList {
|
||||
typeMap[v.Name.Token.Text] = placeholder.PlaceHolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return api.checkTypeContext(typeMap)
|
||||
}
|
||||
|
||||
func (api *API) checkTypeContext(declareContext map[string]placeholder.Type) error {
|
||||
var em = newErrorManager()
|
||||
for _, v := range api.TypeStmt {
|
||||
switch tp := v.(type) {
|
||||
case *ast.TypeLiteralStmt:
|
||||
em.add(api.checkTypeExprContext(declareContext, tp.Expr.DataType))
|
||||
case *ast.TypeGroupStmt:
|
||||
for _, v := range tp.ExprList {
|
||||
em.add(api.checkTypeExprContext(declareContext, v.DataType))
|
||||
}
|
||||
}
|
||||
}
|
||||
return em.error()
|
||||
}
|
||||
|
||||
func (api *API) checkTypeExprContext(declareContext map[string]placeholder.Type, tp ast.DataType) error {
|
||||
switch val := tp.(type) {
|
||||
case *ast.ArrayDataType:
|
||||
return api.checkTypeExprContext(declareContext, val.DataType)
|
||||
case *ast.BaseDataType:
|
||||
if IsBaseType(val.Base.Token.Text) {
|
||||
return nil
|
||||
}
|
||||
_, ok := declareContext[val.Base.Token.Text]
|
||||
if !ok {
|
||||
return ast.SyntaxError(val.Base.Pos(), "unresolved type <%s>", val.Base.Token.Text)
|
||||
}
|
||||
return nil
|
||||
case *ast.MapDataType:
|
||||
var manager = newErrorManager()
|
||||
manager.add(api.checkTypeExprContext(declareContext, val.Key))
|
||||
manager.add(api.checkTypeExprContext(declareContext, val.Value))
|
||||
return manager.error()
|
||||
case *ast.PointerDataType:
|
||||
return api.checkTypeExprContext(declareContext, val.DataType)
|
||||
case *ast.SliceDataType:
|
||||
return api.checkTypeExprContext(declareContext, val.DataType)
|
||||
case *ast.StructDataType:
|
||||
var manager = newErrorManager()
|
||||
for _, e := range val.Elements {
|
||||
manager.add(api.checkTypeExprContext(declareContext, e.DataType))
|
||||
}
|
||||
return manager.error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) getAtServerValue(atServer *ast.AtServerStmt, key string) string {
|
||||
if atServer == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, val := range atServer.Values {
|
||||
if val.Key.Token.Text == key {
|
||||
return val.Value.Token.Text
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (api *API) mergeAPI(in *API) error {
|
||||
for k, v := range in.importManager {
|
||||
api.importManager[k] = v
|
||||
}
|
||||
if api.Syntax.Value.Format() != in.Syntax.Value.Format() {
|
||||
return ast.SyntaxError(in.Syntax.Value.Pos(),
|
||||
"multiple syntax value expression, expected <%s>, got <%s>",
|
||||
api.Syntax.Value.Format(),
|
||||
in.Syntax.Value.Format(),
|
||||
)
|
||||
}
|
||||
api.TypeStmt = append(api.TypeStmt, in.TypeStmt...)
|
||||
api.ServiceStmts = append(api.ServiceStmts, in.ServiceStmts...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) parseImportedAPI(imports []ast.ImportStmt) ([]*API, error) {
|
||||
var list []*API
|
||||
if len(imports) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
var importValueSet = map[string]token.Token{}
|
||||
for _, imp := range imports {
|
||||
switch val := imp.(type) {
|
||||
case *ast.ImportLiteralStmt:
|
||||
importValueSet[strings.ReplaceAll(val.Value.Token.Text, `"`, "")] = val.Value.Token
|
||||
case *ast.ImportGroupStmt:
|
||||
for _, v := range val.Values {
|
||||
importValueSet[strings.ReplaceAll(v.Token.Text, `"`, "")] = v.Token
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dir := filepath.Dir(api.Filename)
|
||||
for impPath, tok := range importValueSet {
|
||||
if !filepath.IsAbs(impPath) {
|
||||
impPath = filepath.Join(dir, impPath)
|
||||
}
|
||||
// import cycle check
|
||||
if _, ok := api.importManager[impPath]; ok {
|
||||
return nil, ast.SyntaxError(tok.Position, "import circle not allowed")
|
||||
} else {
|
||||
api.importManager[impPath] = placeholder.PlaceHolder
|
||||
}
|
||||
|
||||
p := New(impPath, "")
|
||||
ast := p.Parse()
|
||||
if err := p.CheckErrors(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nestedApi, err := convert2API(ast, api.importManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = nestedApi.parseReverse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = append(list, nestedApi)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (api *API) parseReverse() error {
|
||||
list, err := api.parseImportedAPI(api.importStmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range list {
|
||||
if err = api.mergeAPI(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) SelfCheck() error {
|
||||
if err := api.parseReverse(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := api.checkImportStmt(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := api.checkInfoStmt(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := api.checkTypeStmt(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := api.checkServiceStmt(); err != nil {
|
||||
return err
|
||||
}
|
||||
return api.checkTypeDeclareContext()
|
||||
}
|
||||
28
tools/goctl/pkg/parser/api/parser/error.go
Normal file
28
tools/goctl/pkg/parser/api/parser/error.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type errorManager struct {
|
||||
errors []string
|
||||
}
|
||||
|
||||
func newErrorManager() *errorManager {
|
||||
return &errorManager{}
|
||||
}
|
||||
|
||||
func (e *errorManager) add(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
e.errors = append(e.errors, err.Error())
|
||||
}
|
||||
|
||||
func (e *errorManager) error() error {
|
||||
if len(e.errors)==0{
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(strings.Join(e.errors, "\n"))
|
||||
}
|
||||
55
tools/goctl/pkg/parser/api/parser/filter.go
Normal file
55
tools/goctl/pkg/parser/api/parser/filter.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/ast"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/placeholder"
|
||||
)
|
||||
|
||||
type filterBuilder struct {
|
||||
m map[string]placeholder.Type
|
||||
checkExprName string
|
||||
errorManager *errorManager
|
||||
}
|
||||
|
||||
func (b *filterBuilder) check(nodes ...*ast.TokenNode) {
|
||||
for _, node := range nodes {
|
||||
if _, ok := b.m[node.Token.Text]; ok {
|
||||
b.errorManager.add(ast.DuplicateStmtError(node.Pos(), "duplicate "+b.checkExprName))
|
||||
} else {
|
||||
b.m[node.Token.Text] = placeholder.PlaceHolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *filterBuilder) error() error {
|
||||
return b.errorManager.error()
|
||||
}
|
||||
|
||||
type filter struct {
|
||||
builders []*filterBuilder
|
||||
}
|
||||
|
||||
func newFilter() *filter {
|
||||
return &filter{}
|
||||
}
|
||||
|
||||
func (f *filter) addCheckItem(checkExprName string) *filterBuilder {
|
||||
b := &filterBuilder{
|
||||
m: make(map[string]placeholder.Type),
|
||||
checkExprName: checkExprName,
|
||||
errorManager: newErrorManager(),
|
||||
}
|
||||
f.builders = append(f.builders, b)
|
||||
return b
|
||||
}
|
||||
|
||||
func (f *filter) error() error {
|
||||
if len(f.builders) == 0 {
|
||||
return nil
|
||||
}
|
||||
var errorManager = newErrorManager()
|
||||
for _, b := range f.builders {
|
||||
errorManager.add(b.error())
|
||||
}
|
||||
return errorManager.error()
|
||||
}
|
||||
1506
tools/goctl/pkg/parser/api/parser/parser.go
Normal file
1506
tools/goctl/pkg/parser/api/parser/parser.go
Normal file
File diff suppressed because it is too large
Load Diff
1484
tools/goctl/pkg/parser/api/parser/parser_test.go
Normal file
1484
tools/goctl/pkg/parser/api/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load Diff
5
tools/goctl/pkg/parser/api/parser/testdata/atdoc_group_test.api
vendored
Normal file
5
tools/goctl/pkg/parser/api/parser/testdata/atdoc_group_test.api
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
@doc(
|
||||
foo: "foo"
|
||||
bar: "bar"
|
||||
baz: ""
|
||||
)
|
||||
3
tools/goctl/pkg/parser/api/parser/testdata/atdoc_literal_test.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/atdoc_literal_test.api
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
@doc ""
|
||||
@doc "foo"
|
||||
@doc "bar"
|
||||
3
tools/goctl/pkg/parser/api/parser/testdata/athandler_test.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/athandler_test.api
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
@handler foo
|
||||
@handler foo1
|
||||
@handler _bar
|
||||
16
tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api
vendored
Normal file
16
tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
@server(
|
||||
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
|
||||
)
|
||||
11
tools/goctl/pkg/parser/api/parser/testdata/base.api
vendored
Normal file
11
tools/goctl/pkg/parser/api/parser/testdata/base.api
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
syntax = "v1"
|
||||
|
||||
import "base1.api"
|
||||
info (
|
||||
title: "type title here"
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
|
||||
11
tools/goctl/pkg/parser/api/parser/testdata/base1.api
vendored
Normal file
11
tools/goctl/pkg/parser/api/parser/testdata/base1.api
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
syntax = "v1"
|
||||
|
||||
import "base2.api"
|
||||
info (
|
||||
title: "type title here"
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
|
||||
11
tools/goctl/pkg/parser/api/parser/testdata/base2.api
vendored
Normal file
11
tools/goctl/pkg/parser/api/parser/testdata/base2.api
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
syntax = "v1"
|
||||
|
||||
import "base.api"
|
||||
info (
|
||||
title: "type title here"
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
|
||||
4
tools/goctl/pkg/parser/api/parser/testdata/comment_test.api
vendored
Normal file
4
tools/goctl/pkg/parser/api/parser/testdata/comment_test.api
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// foo
|
||||
// bar
|
||||
/*foo*/
|
||||
/*bar*/ //baz
|
||||
167
tools/goctl/pkg/parser/api/parser/testdata/example.api
vendored
Normal file
167
tools/goctl/pkg/parser/api/parser/testdata/example.api
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
syntax = "v1"
|
||||
|
||||
import "example_base1.api"
|
||||
|
||||
import (
|
||||
"example_base2.api"
|
||||
)
|
||||
|
||||
info (
|
||||
title: "type title here"
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
|
||||
type GetFormReq {
|
||||
Name string `form:"name"`
|
||||
Age int `form:"age"`
|
||||
Hobbits []string `form:"hobbits"`
|
||||
}
|
||||
|
||||
type GetFormREsp {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Hobbits []string `json:"hobbits"`
|
||||
}
|
||||
|
||||
type (
|
||||
PostFormReq {
|
||||
Name string `form:"name"`
|
||||
Age int `form:"age"`
|
||||
Hobbits []string `form:"hobbits"`
|
||||
}
|
||||
PostFormResp {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Hobbits []string `json:"hobbits"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
PostJsonReq {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Hobbits []string `json:"hobbits"`
|
||||
}
|
||||
PostJsonResp {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Hobbits []string `json:"hobbits"`
|
||||
Extra map[string]string `json:"extra"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
PostPathReq {
|
||||
Id string `path:"id"`
|
||||
}
|
||||
PostPathResp {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Hobbits []string `json:"hobbits"`
|
||||
Hobbits2 [2]string `json:"hobbits2"`
|
||||
Extra map[string]string `json:"extra"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
DemoOfArrayReq {
|
||||
In string `json:"in"`
|
||||
}
|
||||
DemoOfArrayResp {
|
||||
Out string `json:"out"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
Nest {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
NestDemoReq1 {
|
||||
Nest *Nest `json:"nest"`
|
||||
}
|
||||
NestDemoResp1 {
|
||||
Nest []*Nest `json:"nest"`
|
||||
}
|
||||
NestDemoReq2 {
|
||||
*Nest
|
||||
}
|
||||
NestDemoResp2 {
|
||||
*Nest `json:"nest"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
group: form
|
||||
timeout: 3s
|
||||
)
|
||||
service example {
|
||||
@handler getForm
|
||||
get /example/form (GetFormReq) returns (GetFormREsp)
|
||||
|
||||
@handler postForm
|
||||
post /example/form (PostFormReq) returns (PostFormResp)
|
||||
}
|
||||
|
||||
@server (
|
||||
group: json
|
||||
jwt: Auth
|
||||
timeout: 3m
|
||||
)
|
||||
service example {
|
||||
@doc "json demo"
|
||||
@handler postJson
|
||||
post /example/json (PostJsonReq) returns (PostJsonResp)
|
||||
}
|
||||
|
||||
@server (
|
||||
group: path
|
||||
middleware: Path
|
||||
prefix: /v1/v2
|
||||
timeout: 100ms
|
||||
)
|
||||
service example {
|
||||
@doc (
|
||||
desc: "path demo"
|
||||
)
|
||||
@handler postPath
|
||||
post /example/path (PostPathReq) returns (PostPathResp)
|
||||
}
|
||||
|
||||
@server (
|
||||
group: array
|
||||
prefix: /array
|
||||
maxBytes: 1024
|
||||
)
|
||||
service example {
|
||||
@doc (
|
||||
desc: "array response demo"
|
||||
)
|
||||
@handler getArray
|
||||
post /example/array (DemoOfArrayReq) returns ([]DemoOfArrayResp)
|
||||
|
||||
@doc (
|
||||
desc: "array pointer response demo"
|
||||
)
|
||||
@handler getArrayPointer
|
||||
post /example/array/pointer (DemoOfArrayReq) returns ([]*DemoOfArrayResp)
|
||||
|
||||
@doc (
|
||||
desc: "array base response demo"
|
||||
)
|
||||
@handler getArrayBase
|
||||
post /example/array/base (DemoOfArrayReq) returns ([]string)
|
||||
}
|
||||
|
||||
service example {
|
||||
@handler nestDemo1
|
||||
post /example/nest (NestDemoReq1) returns (NestDemoResp1)
|
||||
|
||||
@handler nestDemo2
|
||||
post /example/nest2 (NestDemoReq2) returns (NestDemoResp2)
|
||||
}
|
||||
|
||||
12
tools/goctl/pkg/parser/api/parser/testdata/example_base1.api
vendored
Normal file
12
tools/goctl/pkg/parser/api/parser/testdata/example_base1.api
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
syntax = "v1"
|
||||
|
||||
info(
|
||||
title: "type title here"
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
|
||||
type BaseReq1{}
|
||||
type BaseResp1{}
|
||||
12
tools/goctl/pkg/parser/api/parser/testdata/example_base2.api
vendored
Normal file
12
tools/goctl/pkg/parser/api/parser/testdata/example_base2.api
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
syntax = "v1"
|
||||
|
||||
info(
|
||||
title: "type title here"
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
|
||||
type BaseReq2{}
|
||||
type BaseResp2{}
|
||||
5
tools/goctl/pkg/parser/api/parser/testdata/import_group_test.api
vendored
Normal file
5
tools/goctl/pkg/parser/api/parser/testdata/import_group_test.api
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import (
|
||||
""
|
||||
"foo"
|
||||
"bar"
|
||||
)
|
||||
3
tools/goctl/pkg/parser/api/parser/testdata/import_literal_test.api
vendored
Normal file
3
tools/goctl/pkg/parser/api/parser/testdata/import_literal_test.api
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import ""
|
||||
import "foo"
|
||||
import "bar"
|
||||
7
tools/goctl/pkg/parser/api/parser/testdata/info_test.api
vendored
Normal file
7
tools/goctl/pkg/parser/api/parser/testdata/info_test.api
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
info(
|
||||
title: "type title here"
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
136
tools/goctl/pkg/parser/api/parser/testdata/invalid.api
vendored
Normal file
136
tools/goctl/pkg/parser/api/parser/testdata/invalid.api
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// test case: expected syntax statement
|
||||
info ()
|
||||
|
||||
-----
|
||||
// test case: duplicate syntax statement
|
||||
syntax = "v1"
|
||||
syntax = "v1"
|
||||
|
||||
-----
|
||||
// test case: duplicate info statement
|
||||
syntax = "v1"
|
||||
info()
|
||||
info()
|
||||
|
||||
-----
|
||||
// test case: duplicate type
|
||||
syntax = "v1"
|
||||
type Foo{}
|
||||
type Foo{}
|
||||
|
||||
-----
|
||||
// test case: duplicate type
|
||||
syntax = "v1"
|
||||
type Baz{}
|
||||
type (
|
||||
Baz{}
|
||||
)
|
||||
|
||||
|
||||
-----
|
||||
// test case: multiple service name
|
||||
syntax = "v1"
|
||||
service foo{
|
||||
@handler foo
|
||||
get /foo
|
||||
}
|
||||
service bar{
|
||||
@handler foo
|
||||
get /foo
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: duplicate handler
|
||||
syntax = "v1"
|
||||
service foo{
|
||||
@handler foo
|
||||
get /foo
|
||||
@handler foo
|
||||
get /bar
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: duplicate path
|
||||
syntax = "v1"
|
||||
service foo{
|
||||
@handler foo
|
||||
get /foo
|
||||
@handler bar
|
||||
get /foo
|
||||
@handler qux
|
||||
get /v1/baz
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: /v1
|
||||
)
|
||||
service foo{
|
||||
@handler qux
|
||||
get /baz
|
||||
@handler quux
|
||||
get /baz
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: type declare context
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar Bar `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: map key expected literal type
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar map[[]int]string `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: map key expected literal type
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar map[[]int]string `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: map key expected literal type
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar *map[[]int]string `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: map valu expected literal type
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar *map[string]{} `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: invalid slice
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar []map[[]int]string `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: array
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar [...]int `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: any
|
||||
syntax = "v1"
|
||||
type Foo {
|
||||
Bar any `json:"bar"`
|
||||
}
|
||||
|
||||
-----
|
||||
// test case: unresolved type
|
||||
syntax = "v1"
|
||||
service example {
|
||||
@handler nestDemo
|
||||
post /example/nest (NestDemoReq) returns (NestDemoResp)
|
||||
}
|
||||
37
tools/goctl/pkg/parser/api/parser/testdata/service_test.api
vendored
Normal file
37
tools/goctl/pkg/parser/api/parser/testdata/service_test.api
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
service foo {
|
||||
@handler bar
|
||||
get /ping
|
||||
|
||||
@handler bar
|
||||
get /ping;
|
||||
}
|
||||
|
||||
service bar {
|
||||
@doc "bar"
|
||||
@handler foo
|
||||
get /foo/:bar (Foo)
|
||||
|
||||
@doc "bar"
|
||||
@handler foo
|
||||
get /foo/:bar (Foo) returns ();
|
||||
|
||||
@handler foo
|
||||
get /foo/:bar returns (Foo)
|
||||
|
||||
@handler foo
|
||||
get /foo/:bar () returns (Foo);
|
||||
}
|
||||
|
||||
service baz-api {
|
||||
@handler foo
|
||||
post /foo/:bar/foo-bar-baz (Foo) returns (*Bar)
|
||||
|
||||
@handler foo
|
||||
post /foo/:bar/foo-bar-baz (Foo) returns (*Bar);
|
||||
|
||||
@handler bar
|
||||
post /foo ([]Foo) returns ([]*Bar)
|
||||
|
||||
@handler bar
|
||||
post /foo ([]Foo) returns ([]*Bar);
|
||||
}
|
||||
160
tools/goctl/pkg/parser/api/parser/testdata/test.api
vendored
Normal file
160
tools/goctl/pkg/parser/api/parser/testdata/test.api
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// aaaa
|
||||
|
||||
/*bb*/ syntax /*cc*/ = /*dd*/ "v1" /*syntax doc*/ // syntax stmt
|
||||
// bbb
|
||||
|
||||
info ( // info stmt
|
||||
title: "type title here" // title expr
|
||||
/*ee*/
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
email: "type email here"
|
||||
version: "type version here"
|
||||
)
|
||||
|
||||
type AliasInt int
|
||||
type AliasString = string
|
||||
type AliasArray [2]int8
|
||||
type AliasArray2 [...]int8
|
||||
type AliasSlice []int8
|
||||
type AliasMap map[string]int
|
||||
type Any interface{}
|
||||
type AliasMapKeyStruct map[{
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
}
|
||||
}
|
||||
}]int
|
||||
type AliasMapValueStruct map[string]{
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type Foo {
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Bar {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Bar {
|
||||
Base int `json:"base"`
|
||||
Array1 [2]int `json:"array1"`
|
||||
Array2 [...]int `json:"array2"`
|
||||
Slice []int `json:"slice"`
|
||||
Map1 map[string]int `json:"map1"`
|
||||
Map2 map[string]*int `json:"map2"`
|
||||
Map3 map[string][]int `json:"map3"`
|
||||
Map4 map[string][]*int `json:"map4"`
|
||||
Map5 map[string][2]int `json:"map5"`
|
||||
Map6 map[string][...]int `json:"map6"`
|
||||
Interface interface{} `json:"interface"`
|
||||
Any any `json:"any"`
|
||||
Foo Foo `json:"foo"`
|
||||
Baz {
|
||||
F1 int `json:"f1"`
|
||||
F2 int `json:"f2"`
|
||||
} `json:"baz"`
|
||||
Qux *string `json:"qux"`
|
||||
Quux bool `json:"quux"`
|
||||
}
|
||||
|
||||
type (
|
||||
GroupAliasInt int
|
||||
GroupAliasString = string
|
||||
GroupAliasArray [2]int8
|
||||
GroupAliasArray2 [...]int8
|
||||
GroupAliasSlice []int8
|
||||
GroupAliasMap map[string]int
|
||||
GroupAny interface{}
|
||||
GroupFoo {}
|
||||
GroupBar {
|
||||
Base int `json:"base"`
|
||||
Array1 [2]int `json:"array1"`
|
||||
Array2 [...]int `json:"array2"`
|
||||
Slice []int `json:"slice"`
|
||||
Map1 map[string]int `json:"map1"`
|
||||
Map2 map[string]*int `json:"map2"`
|
||||
Map3 map[string][]int `json:"map3"`
|
||||
Map4 map[string][]*int `json:"map4"`
|
||||
Map5 map[string][2]int `json:"map5"`
|
||||
Map6 map[string][...]int `json:"map6"`
|
||||
Interface interface{} `json:"interface"`
|
||||
Any any `json:"any"`
|
||||
Foo Foo `json:"foo"`
|
||||
Baz {
|
||||
F1 int `json:"f1"`
|
||||
F2 int `json:"f2"`
|
||||
} `json:"baz"`
|
||||
Qux *string `json:"qux"`
|
||||
Quux bool `json:"quux"`
|
||||
}
|
||||
)
|
||||
|
||||
@server ()
|
||||
service test {
|
||||
@handler foo
|
||||
get /test/foo
|
||||
}
|
||||
|
||||
@server (
|
||||
jwt: Auth
|
||||
group: Group1
|
||||
)
|
||||
service test {
|
||||
@doc "ping"
|
||||
@handler foo
|
||||
get /test/foo
|
||||
@doc (
|
||||
key1: "value1"
|
||||
key2: "value2"
|
||||
)
|
||||
@handler bar
|
||||
get /test/foo (Foo)
|
||||
@handler baz
|
||||
post /test/foo/baz returns (Bar)
|
||||
@handler qux
|
||||
post /test/foo/baz/:qux (Foo) returns (Bar)
|
||||
@handler quux
|
||||
post /test/foo/baz/:qux/qu-ux (Foo) returns (Bar)
|
||||
@handler foobar
|
||||
post /foo/bar (*Foo) returns ([]Bar)
|
||||
@handler barbaz
|
||||
post /bar/baz ([]*Foo) returns ([]int)
|
||||
}
|
||||
|
||||
// terminal
|
||||
// terminal2
|
||||
/*
|
||||
kkk
|
||||
*/
|
||||
|
||||
50
tools/goctl/pkg/parser/api/parser/testdata/test_format.api
vendored
Normal file
50
tools/goctl/pkg/parser/api/parser/testdata/test_format.api
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// format api demo
|
||||
syntax ="v1" // dd
|
||||
|
||||
info()
|
||||
info(foo:"")
|
||||
info(foo:""
|
||||
bar: ""
|
||||
quux: "")
|
||||
info(foo:""
|
||||
bar: ""
|
||||
quux: ""
|
||||
)
|
||||
// info statement
|
||||
// info statement
|
||||
info (// Info bloack
|
||||
title: "type title here" // title comment
|
||||
desc: "type desc here"
|
||||
author: "type author here"
|
||||
/*aaa*/
|
||||
/*
|
||||
bbb
|
||||
*/
|
||||
email: "type email here" // eamil comment
|
||||
/*aaa*/version:/*bbb*/ "type version here"// version comment
|
||||
)
|
||||
|
||||
import ""
|
||||
import "aaa"
|
||||
import"bb"
|
||||
import "cc"
|
||||
import()
|
||||
import(
|
||||
)
|
||||
import (
|
||||
|
||||
)
|
||||
import ("aa")
|
||||
import ("aa" "bb")
|
||||
import ("aa"
|
||||
"bb"
|
||||
)
|
||||
import ("aa"
|
||||
"bb")
|
||||
import (
|
||||
|
||||
"aa"
|
||||
|
||||
"bb"
|
||||
|
||||
)
|
||||
Reference in New Issue
Block a user