package parser import ( "fmt" "sort" "strings" "github.com/zeromicro/go-zero/core/lang" "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/importstack" "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 { if _, ok := dt.(*ast.BaseDataType); 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 { a.fillInfo() if err := a.fillTypes(); err != nil { return err } if err := a.fillService(); err != nil { return err } sort.SliceStable(a.spec.Types, func(i, j int) bool { return a.spec.Types[i].Name() < a.spec.Types[j].Name() }) groups := make([]spec.Group, 0, len(a.spec.Service.Groups)) for _, v := range a.spec.Service.Groups { sort.SliceStable(v.Routes, func(i, j int) bool { return v.Routes[i].Path < v.Routes[j].Path }) groups = append(groups, v) } sort.SliceStable(groups, func(i, j int) bool { return groups[i].Annotation.Properties[groupKeyText] < groups[j].Annotation.Properties[groupKeyText] }) a.spec.Service.Groups = groups return nil } 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, ":") if key == summaryKeyText { ret[key] = v.Value.RawText() } else { 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) fillInfo() { properties := make(map[string]string) if a.api.info != nil { for _, kv := range a.api.info.Values { key := kv.Key.Token.Text properties[strings.TrimSuffix(key, ":")] = kv.Value.RawText() } } a.spec.Info.Properties = properties infoKeyValue := make(map[string]string) for key, value := range properties { titleKey := strings.Title(strings.TrimSuffix(key, ":")) infoKeyValue[titleKey] = value } a.spec.Info.Title = infoKeyValue[infoTitleKey] a.spec.Info.Desc = infoKeyValue[infoDescKey] a.spec.Info.Version = infoKeyValue[infoVersionKey] a.spec.Info.Author = infoKeyValue[infoAuthorKey] a.spec.Info.Email = infoKeyValue[infoEmailKey] } func (a *Analyzer) fillTypes() error { for _, item := range a.api.TypeStmt { switch v := (item).(type) { case *ast.TypeLiteralStmt: if err := a.fillTypeExpr(v.Expr); 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 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 } is := importstack.New() err := is.Push(ast.Filename) if err != nil { return nil, err } importSet := map[string]lang.PlaceholderType{} api, err := convert2API(ast, importSet, is) if err != nil { return nil, err } if err := api.SelfCheck(); 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 }