Improvements for binapi-generator and support VPP 19.04 in statsclient
[govpp.git] / cmd / binapi-generator / generate.go
index 1ff3e4c..e386f8d 100644 (file)
@@ -41,6 +41,10 @@ const (
        constVersionCrc = "VersionCrc" // version CRC constant
 
        unionDataField = "XXX_UnionData" // name for the union data field
+
+       serviceApiName    = "RPCService"    // name for the RPC service interface
+       serviceImplName   = "serviceClient" // name for the RPC service implementation
+       serviceClientName = "ServiceClient" // name for the RPC service client
 )
 
 // context is a structure storing data for code generation
@@ -61,8 +65,8 @@ type context struct {
        packageData *Package // parsed package data
 }
 
-// getContext returns context details of the code generation task
-func getContext(inputFile, outputDir string) (*context, error) {
+// newContext returns context details of the code generation task
+func newContext(inputFile, outputDir string) (*context, error) {
        if !strings.HasSuffix(inputFile, inputFileExt) {
                return nil, fmt.Errorf("invalid input file name: %q", inputFile)
        }
@@ -93,13 +97,14 @@ func getContext(inputFile, outputDir string) (*context, error) {
        return ctx, nil
 }
 
-// generatePackage generates code for the parsed package data and writes it into w
 func generatePackage(ctx *context, w io.Writer) error {
        logf("generating package %q", ctx.packageName)
 
-       // generate file header
+       fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+       fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
+       fmt.Fprintln(w)
+
        generateHeader(ctx, w)
-       generateImports(ctx, w)
 
        // generate module desc
        fmt.Fprintln(w, "const (")
@@ -119,8 +124,6 @@ func generatePackage(ctx *context, w io.Writer) error {
 
        // generate enums
        if len(ctx.packageData.Enums) > 0 {
-               fmt.Fprintf(w, "/* Enums */\n\n")
-
                for _, enum := range ctx.packageData.Enums {
                        generateEnum(ctx, w, &enum)
                }
@@ -128,8 +131,6 @@ func generatePackage(ctx *context, w io.Writer) error {
 
        // generate aliases
        if len(ctx.packageData.Aliases) > 0 {
-               fmt.Fprintf(w, "/* Aliases */\n\n")
-
                for _, alias := range ctx.packageData.Aliases {
                        generateAlias(ctx, w, &alias)
                }
@@ -137,8 +138,6 @@ func generatePackage(ctx *context, w io.Writer) error {
 
        // generate types
        if len(ctx.packageData.Types) > 0 {
-               fmt.Fprintf(w, "/* Types */\n\n")
-
                for _, typ := range ctx.packageData.Types {
                        generateType(ctx, w, &typ)
                }
@@ -146,8 +145,6 @@ func generatePackage(ctx *context, w io.Writer) error {
 
        // generate unions
        if len(ctx.packageData.Unions) > 0 {
-               fmt.Fprintf(w, "/* Unions */\n\n")
-
                for _, union := range ctx.packageData.Unions {
                        generateUnion(ctx, w, &union)
                }
@@ -155,8 +152,6 @@ func generatePackage(ctx *context, w io.Writer) error {
 
        // generate messages
        if len(ctx.packageData.Messages) > 0 {
-               fmt.Fprintf(w, "/* Messages */\n\n")
-
                for _, msg := range ctx.packageData.Messages {
                        generateMessage(ctx, w, &msg)
                }
@@ -189,20 +184,17 @@ func generatePackage(ctx *context, w io.Writer) error {
                }
        }
 
+       generateFooter(ctx, w)
+
        return nil
 }
 
-// generateHeader writes generated package header into w
 func generateHeader(ctx *context, w io.Writer) {
-       fmt.Fprintln(w, "// Code generated by GoVPP binapi-generator. DO NOT EDIT.")
-       fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
-       fmt.Fprintln(w)
-
        fmt.Fprintln(w, "/*")
-       fmt.Fprintf(w, "Package %s is a generated from VPP binary API module '%s'.\n", ctx.packageName, ctx.moduleName)
+       fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
        fmt.Fprintln(w)
-       fmt.Fprintf(w, " The %s module consists of:\n", ctx.moduleName)
-       var printObjNum = func(obj string, num int) {
+       fmt.Fprintln(w, "It consists of:")
+       printObjNum := func(obj string, num int) {
                if num > 0 {
                        if num > 1 {
                                if strings.HasSuffix(obj, "s") {
@@ -215,7 +207,6 @@ func generateHeader(ctx *context, w io.Writer) {
                        fmt.Fprintf(w, "\t%3d %s\n", num, obj)
                }
        }
-
        printObjNum("enum", len(ctx.packageData.Enums))
        printObjNum("alias", len(ctx.packageData.Aliases))
        printObjNum("type", len(ctx.packageData.Types))
@@ -223,42 +214,42 @@ func generateHeader(ctx *context, w io.Writer) {
        printObjNum("message", len(ctx.packageData.Messages))
        printObjNum("service", len(ctx.packageData.Services))
        fmt.Fprintln(w, "*/")
-
        fmt.Fprintf(w, "package %s\n", ctx.packageName)
        fmt.Fprintln(w)
+
+       fmt.Fprintln(w, "import (")
+       fmt.Fprintf(w, "\tapi \"%s\"\n", govppApiImportPath)
+       fmt.Fprintf(w, "\tbytes \"%s\"\n", "bytes")
+       fmt.Fprintf(w, "\tcontext \"%s\"\n", "context")
+       fmt.Fprintf(w, "\tio \"%s\"\n", "io")
+       fmt.Fprintf(w, "\tstrconv \"%s\"\n", "strconv")
+       fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
+       fmt.Fprintln(w, ")")
+       fmt.Fprintln(w)
 }
 
-// generateImports writes generated package imports into w
-func generateImports(ctx *context, w io.Writer) {
-       fmt.Fprintf(w, "import api \"%s\"\n", govppApiImportPath)
-       fmt.Fprintf(w, "import bytes \"%s\"\n", "bytes")
-       fmt.Fprintf(w, "import context \"%s\"\n", "context")
-       fmt.Fprintf(w, "import strconv \"%s\"\n", "strconv")
-       fmt.Fprintf(w, "import struc \"%s\"\n", "github.com/lunixbochs/struc")
+func generateFooter(ctx *context, w io.Writer) {
+       fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
+       fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
+       fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
+       fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
+       fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
        fmt.Fprintln(w)
 
        fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
        fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
        fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
        fmt.Fprintf(w, "var _ = context.Background\n")
+       fmt.Fprintf(w, "var _ = io.Copy\n")
        fmt.Fprintf(w, "var _ = strconv.Itoa\n")
        fmt.Fprintf(w, "var _ = struc.Pack\n")
-       fmt.Fprintln(w)
-
-       fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
-       fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
-       fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
-       fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
-       fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
-       fmt.Fprintln(w)
 }
 
-// generateComment writes generated comment for the object into w
 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
        if objKind == "service" {
-               fmt.Fprintf(w, "// %s represents VPP binary API services in %s module.\n", goName, ctx.moduleName)
+               fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
        } else {
-               fmt.Fprintf(w, "// %s represents VPP binary API %s '%s':\n", goName, objKind, vppName)
+               fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
        }
 
        if !ctx.includeComments {
@@ -315,97 +306,6 @@ func generateComment(ctx *context, w io.Writer, goName string, vppName string, o
        fmt.Fprintln(w, "//")
 }
 
-// generateServices writes generated code for the Services interface into w
-func generateServices(ctx *context, w io.Writer, services []Service) {
-       const apiName = "Service"
-       const implName = "service"
-
-       // generate services comment
-       generateComment(ctx, w, apiName, "services", "service")
-
-       // generate interface
-       fmt.Fprintf(w, "type %s interface {\n", apiName)
-       for _, svc := range services {
-               generateServiceMethod(ctx, w, &svc)
-               fmt.Fprintln(w)
-       }
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       // generate client implementation
-       fmt.Fprintf(w, "type %s struct {\n", implName)
-       fmt.Fprintf(w, "\tch api.Channel\n")
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       fmt.Fprintf(w, "func New%[1]s(ch api.Channel) %[1]s {\n", apiName)
-       fmt.Fprintf(w, "\treturn &%s{ch}\n", implName)
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       for _, svc := range services {
-               fmt.Fprintf(w, "func (c *%s) ", implName)
-               generateServiceMethod(ctx, w, &svc)
-               fmt.Fprintln(w, " {")
-               if svc.Stream {
-                       // TODO: stream responses
-                       //fmt.Fprintf(w, "\tstream := make(chan *%s)\n", camelCaseName(svc.ReplyType))
-                       replyTyp := camelCaseName(svc.ReplyType)
-                       fmt.Fprintf(w, "\tvar dump []*%s\n", replyTyp)
-                       fmt.Fprintf(w, "\treq := c.ch.SendMultiRequest(in)\n")
-                       fmt.Fprintf(w, "\tfor {\n")
-                       fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
-                       fmt.Fprintf(w, "\tstop, err := req.ReceiveReply(m)\n")
-                       fmt.Fprintf(w, "\tif stop { break }\n")
-                       fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
-                       fmt.Fprintf(w, "\tdump = append(dump, m)\n")
-                       fmt.Fprintln(w, "}")
-                       fmt.Fprintf(w, "\treturn dump, nil\n")
-               } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
-                       fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
-                       fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
-                       fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
-                       fmt.Fprintf(w, "\treturn out, nil\n")
-               } else {
-                       fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
-                       fmt.Fprintf(w, "\treturn nil\n")
-               }
-               fmt.Fprintln(w, "}")
-               fmt.Fprintln(w)
-       }
-
-       fmt.Fprintln(w)
-}
-
-// generateServiceMethod writes generated code for the service into w
-func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
-       reqTyp := camelCaseName(svc.RequestType)
-
-       // method name is same as parameter type name by default
-       method := reqTyp
-       if svc.Stream {
-               // use Dump as prefix instead of suffix for stream services
-               if m := strings.TrimSuffix(method, "Dump"); method != m {
-                       method = "Dump" + m
-               }
-       }
-
-       params := fmt.Sprintf("in *%s", reqTyp)
-       returns := "error"
-       if replyType := camelCaseName(svc.ReplyType); replyType != "" {
-               replyTyp := fmt.Sprintf("*%s", replyType)
-               if svc.Stream {
-                       // TODO: stream responses
-                       //replyTyp = fmt.Sprintf("<-chan %s", replyTyp)
-                       replyTyp = fmt.Sprintf("[]%s", replyTyp)
-               }
-               returns = fmt.Sprintf("(%s, error)", replyTyp)
-       }
-
-       fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
-}
-
-// generateEnum writes generated code for the enum into w
 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
        name := camelCaseName(enum.Name)
        typ := binapiTypes[enum.Type]
@@ -450,7 +350,6 @@ func generateEnum(ctx *context, w io.Writer, enum *Enum) {
        fmt.Fprintln(w)
 }
 
-// generateAlias writes generated code for the alias into w
 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
        name := camelCaseName(alias.Name)
 
@@ -472,7 +371,6 @@ func generateAlias(ctx *context, w io.Writer, alias *Alias) {
        fmt.Fprintln(w)
 }
 
-// generateUnion writes generated code for the union into w
 func generateUnion(ctx *context, w io.Writer, union *Union) {
        name := camelCaseName(union.Name)
 
@@ -561,7 +459,6 @@ func (u *%[1]s) Get%[2]s() (a %[3]s) {
 `, structName, getterField, getterStruct, unionDataField)
 }
 
-// generateType writes generated code for the type into w
 func generateType(ctx *context, w io.Writer, typ *Type) {
        name := camelCaseName(typ.Name)
 
@@ -598,7 +495,6 @@ func generateType(ctx *context, w io.Writer, typ *Type) {
        fmt.Fprintln(w)
 }
 
-// generateMessage writes generated code for the message into w
 func generateMessage(ctx *context, w io.Writer, msg *Message) {
        name := camelCaseName(msg.Name)
 
@@ -666,7 +562,6 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) {
        fmt.Fprintln(w)
 }
 
-// generateField writes generated code for the field into w
 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
        field := fields[i]
 
@@ -737,7 +632,6 @@ func generateField(ctx *context, w io.Writer, fields []Field, i int) {
        fmt.Fprintln(w)
 }
 
-// generateMessageNameGetter generates getter for original VPP message name into the provider writer
 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
        fmt.Fprintf(w, `func (*%s) GetMessageName() string {
        return %q
@@ -745,7 +639,6 @@ func generateMessageNameGetter(w io.Writer, structName, msgName string) {
 `, structName, msgName)
 }
 
-// generateTypeNameGetter generates getter for original VPP type name into the provider writer
 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
        fmt.Fprintf(w, `func (*%s) GetTypeName() string {
        return %q
@@ -753,7 +646,6 @@ func generateTypeNameGetter(w io.Writer, structName, msgName string) {
 `, structName, msgName)
 }
 
-// generateCrcGetter generates getter for CRC checksum of the message definition into the provider writer
 func generateCrcGetter(w io.Writer, structName, crc string) {
        crc = strings.TrimPrefix(crc, "0x")
        fmt.Fprintf(w, `func (*%s) GetCrcString() string {
@@ -762,7 +654,6 @@ func generateCrcGetter(w io.Writer, structName, crc string) {
 `, structName, crc)
 }
 
-// generateMessageTypeGetter generates message factory for the generated message into the provider writer
 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
        fmt.Fprintln(w, "func (*"+structName+") GetMessageType() api.MessageType {")
        if msgType == requestMessage {
@@ -776,3 +667,116 @@ func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageTy
        }
        fmt.Fprintln(w, "}")
 }
+
+func generateServices(ctx *context, w io.Writer, services []Service) {
+
+       // generate services comment
+       generateComment(ctx, w, serviceApiName, "services", "service")
+
+       // generate service api
+       fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
+       for _, svc := range services {
+               generateServiceMethod(ctx, w, &svc)
+               fmt.Fprintln(w)
+       }
+       fmt.Fprintln(w, "}")
+       fmt.Fprintln(w)
+
+       // generate client implementation
+       fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
+       fmt.Fprintf(w, "\tch api.Channel\n")
+       fmt.Fprintln(w, "}")
+       fmt.Fprintln(w)
+
+       // generate client constructor
+       fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
+       fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
+       fmt.Fprintln(w, "}")
+       fmt.Fprintln(w)
+
+       for _, svc := range services {
+               method := camelCaseName(svc.RequestType)
+               if m := strings.TrimSuffix(method, "Dump"); method != m {
+                       method = "Dump" + m
+               }
+
+               fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
+               generateServiceMethod(ctx, w, &svc)
+               fmt.Fprintln(w, " {")
+               if svc.Stream {
+                       streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
+                       fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
+                       fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
+                       fmt.Fprintf(w, "\treturn x, nil\n")
+               } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
+                       fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
+                       fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
+                       fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
+                       fmt.Fprintf(w, "\treturn out, nil\n")
+               } else {
+                       fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
+                       fmt.Fprintf(w, "\treturn nil\n")
+               }
+               fmt.Fprintln(w, "}")
+               fmt.Fprintln(w)
+
+               if svc.Stream {
+                       replyTyp := camelCaseName(svc.ReplyType)
+                       method := camelCaseName(svc.RequestType)
+                       if m := strings.TrimSuffix(method, "Dump"); method != m {
+                               method = "Dump" + m
+                       }
+                       streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
+
+                       fmt.Fprintf(w, "type %s interface {\n", streamApi)
+                       fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
+                       fmt.Fprintln(w, "}")
+                       fmt.Fprintln(w)
+
+                       streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
+                       fmt.Fprintf(w, "type %s struct {\n", streamImpl)
+                       fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
+                       fmt.Fprintln(w, "}")
+                       fmt.Fprintln(w)
+
+                       fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
+                       fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
+                       fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
+                       fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
+                       fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
+                       fmt.Fprintf(w, "\treturn m, nil\n")
+                       fmt.Fprintln(w, "}")
+                       fmt.Fprintln(w)
+               }
+       }
+
+       fmt.Fprintln(w)
+}
+
+func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
+       reqTyp := camelCaseName(svc.RequestType)
+
+       // method name is same as parameter type name by default
+       method := reqTyp
+       if svc.Stream {
+               // use Dump as prefix instead of suffix for stream services
+               if m := strings.TrimSuffix(method, "Dump"); method != m {
+                       method = "Dump" + m
+               }
+       }
+
+       params := fmt.Sprintf("in *%s", reqTyp)
+       returns := "error"
+
+       if replyType := camelCaseName(svc.ReplyType); replyType != "" {
+               var replyTyp string
+               if svc.Stream {
+                       replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
+               } else {
+                       replyTyp = fmt.Sprintf("*%s", replyType)
+               }
+               returns = fmt.Sprintf("(%s, error)", replyTyp)
+       }
+
+       fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
+}