Format generated Go source code in-process
[govpp.git] / cmd / binapi-generator / generate.go
index d9555e7..715836d 100644 (file)
@@ -18,6 +18,8 @@ import (
        "bytes"
        "fmt"
        "io"
+       "os/exec"
+       "path"
        "path/filepath"
        "sort"
        "strings"
@@ -34,13 +36,15 @@ const (
        inputFileExt  = ".api.json" // file extension of the VPP API files
        outputFileExt = ".ba.go"    // file extension of the Go generated files
 
-       govppApiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API package
-
        constModuleName = "ModuleName" // module name constant
        constAPIVersion = "APIVersion" // API version constant
        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
@@ -48,6 +52,8 @@ type context struct {
        inputFile  string // input file with VPP API in JSON
        outputFile string // output file with generated Go package
 
+       importPrefix string // defines import path prefix for importing types
+
        inputData []byte // contents of the input file
 
        includeAPIVersion  bool // include constant with API version string
@@ -61,8 +67,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,11 +99,15 @@ 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("----------------------------")
        logf("generating package %q", ctx.packageName)
+       logf("----------------------------")
+
+       fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+       fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
+       fmt.Fprintln(w)
 
-       // generate file header
        generateHeader(ctx, w)
        generateImports(ctx, w)
 
@@ -119,44 +129,50 @@ 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 {
+                       if imp, ok := ctx.packageData.Imports[enum.Name]; ok {
+                               generateImportedAlias(ctx, w, enum.Name, &imp)
+                               continue
+                       }
                        generateEnum(ctx, w, &enum)
                }
        }
 
        // generate aliases
        if len(ctx.packageData.Aliases) > 0 {
-               fmt.Fprintf(w, "/* Aliases */\n\n")
-
                for _, alias := range ctx.packageData.Aliases {
+                       if imp, ok := ctx.packageData.Imports[alias.Name]; ok {
+                               generateImportedAlias(ctx, w, alias.Name, &imp)
+                               continue
+                       }
                        generateAlias(ctx, w, &alias)
                }
        }
 
        // generate types
        if len(ctx.packageData.Types) > 0 {
-               fmt.Fprintf(w, "/* Types */\n\n")
-
                for _, typ := range ctx.packageData.Types {
+                       if imp, ok := ctx.packageData.Imports[typ.Name]; ok {
+                               generateImportedAlias(ctx, w, typ.Name, &imp)
+                               continue
+                       }
                        generateType(ctx, w, &typ)
                }
        }
 
        // generate unions
        if len(ctx.packageData.Unions) > 0 {
-               fmt.Fprintf(w, "/* Unions */\n\n")
-
                for _, union := range ctx.packageData.Unions {
+                       if imp, ok := ctx.packageData.Imports[union.Name]; ok {
+                               generateImportedAlias(ctx, w, union.Name, &imp)
+                               continue
+                       }
                        generateUnion(ctx, w, &union)
                }
        }
 
        // 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 +205,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 +228,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 +235,81 @@ 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)
+
 }
 
-// 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")
+       fmt.Fprintln(w, "import (")
+       fmt.Fprintln(w, `       "bytes"`)
+       fmt.Fprintln(w, `       "context"`)
+       fmt.Fprintln(w, `       "io"`)
+       fmt.Fprintln(w, `       "strconv"`)
        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 _ = strconv.Itoa\n")
-       fmt.Fprintf(w, "var _ = struc.Pack\n")
+       fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
+       fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
+       if len(ctx.packageData.Imports) > 0 {
+               fmt.Fprintln(w)
+               for _, imp := range getImports(ctx) {
+                       importPath := path.Join(ctx.importPrefix, imp)
+                       if importPath == "" {
+                               importPath = getImportPath(filepath.Dir(ctx.outputFile), imp)
+                       }
+                       fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
+               }
+       }
+       fmt.Fprintln(w, ")")
        fmt.Fprintln(w)
+}
+
+func getImportPath(outputDir string, pkg string) string {
+       absPath, _ := filepath.Abs(filepath.Join(outputDir, "..", pkg))
+       cmd := exec.Command("go", "list", absPath)
+       var errbuf, outbuf bytes.Buffer
+       cmd.Stdout = &outbuf
+       cmd.Stderr = &errbuf
+       if err := cmd.Run(); err != nil {
+               fmt.Printf("ERR: %v\n", errbuf.String())
+               panic(err)
+       }
+       return outbuf.String()
+}
+
+func getImports(ctx *context) (imports []string) {
+       impmap := map[string]struct{}{}
+       for _, imp := range ctx.packageData.Imports {
+               if _, ok := impmap[imp.Package]; !ok {
+                       imports = append(imports, imp.Package)
+                       impmap[imp.Package] = struct{}{}
+               }
+       }
+       sort.Strings(imports)
+       return imports
+}
 
+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")
 }
 
-// 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", ctx.moduleName, goName)
+               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 +366,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 +410,14 @@ func generateEnum(ctx *context, w io.Writer, enum *Enum) {
        fmt.Fprintln(w)
 }
 
-// generateAlias writes generated code for the alias into w
+func generateImportedAlias(ctx *context, w io.Writer, tName string, imp *Import) {
+       name := camelCaseName(tName)
+
+       fmt.Fprintf(w, "type %s = %s.%s\n", name, imp.Package, name)
+
+       fmt.Fprintln(w)
+}
+
 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
        name := camelCaseName(alias.Name)
 
@@ -472,7 +439,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)
 
@@ -497,7 +463,9 @@ func generateUnion(ctx *context, w io.Writer, union *Union) {
        generateTypeNameGetter(w, name, union.Name)
 
        // generate CRC getter
-       generateCrcGetter(w, name, union.CRC)
+       if union.CRC != "" {
+               generateCrcGetter(w, name, union.CRC)
+       }
 
        // generate getters for fields
        for _, field := range union.Fields {
@@ -559,7 +527,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)
 
@@ -589,12 +556,13 @@ func generateType(ctx *context, w io.Writer, typ *Type) {
        generateTypeNameGetter(w, name, typ.Name)
 
        // generate CRC getter
-       generateCrcGetter(w, name, typ.CRC)
+       if typ.CRC != "" {
+               generateCrcGetter(w, name, typ.CRC)
+       }
 
        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)
 
@@ -650,45 +618,46 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) {
        // generate end of the struct
        fmt.Fprintln(w, "}")
 
-       // generate name getter
+       // generate message methods
+       generateMessageResetMethod(w, name)
        generateMessageNameGetter(w, name, msg.Name)
-
-       // generate CRC getter
        generateCrcGetter(w, name, msg.CRC)
-
-       // generate message type getter method
        generateMessageTypeGetter(w, name, msgType)
 
        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]
 
        fieldName := strings.TrimPrefix(field.Name, "_")
        fieldName = camelCaseName(fieldName)
 
+       dataType := convertToGoType(ctx, field.Type)
+       fieldType := dataType
+
        // generate length field for strings
-       if field.Type == "string" {
+       if field.Type == "string" && field.Length == 0 {
                fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
        }
 
-       dataType := convertToGoType(ctx, field.Type)
-       fieldType := dataType
-
        // check if it is array
        if field.Length > 0 || field.SizeFrom != "" {
                if dataType == "uint8" {
                        dataType = "byte"
                }
-               fieldType = "[]" + dataType
+               if dataType == "string" && field.SpecifiedLen {
+                       fieldType = "string"
+                       dataType = "byte"
+               } else {
+                       fieldType = "[]" + dataType
+               }
        }
        fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
 
        fieldTags := map[string]string{}
 
-       if field.Length > 0 {
+       if field.Length > 0 && field.SpecifiedLen {
                // fixed size array
                fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
        } else {
@@ -733,42 +702,147 @@ 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
+func generateMessageResetMethod(w io.Writer, structName string) {
+       fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
 }
-`, structName, msgName)
+
+func generateMessageNameGetter(w io.Writer, structName, msgName string) {
+       fmt.Fprintf(w, "func (*%s) GetMessageName() string {    return %q }\n", 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
-}
-`, structName, msgName)
+       fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", 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 {
-       return %q
-}
-`, structName, crc)
+       fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", 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 {")
+       fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {")
        if msgType == requestMessage {
-               fmt.Fprintln(w, "\treturn api.RequestMessage")
+               fmt.Fprintf(w, "\treturn api.RequestMessage")
        } else if msgType == replyMessage {
-               fmt.Fprintln(w, "\treturn api.ReplyMessage")
+               fmt.Fprintf(w, "\treturn api.ReplyMessage")
        } else if msgType == eventMessage {
-               fmt.Fprintln(w, "\treturn api.EventMessage")
+               fmt.Fprintf(w, "\treturn api.EventMessage")
        } else {
-               fmt.Fprintln(w, "\treturn api.OtherMessage")
+               fmt.Fprintf(w, "\treturn api.OtherMessage")
        }
        fmt.Fprintln(w, "}")
+       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)
 }