1 // Copyright (c) 2017 Cisco and/or its affiliates.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
29 // generatedCodeVersion indicates a version of the generated code.
30 // It is incremented whenever an incompatibility between the generated code and
31 // GoVPP api package is introduced; the generated code references
32 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
33 const generatedCodeVersion = 1
36 inputFileExt = ".api.json" // file extension of the VPP API files
37 outputFileExt = ".ba.go" // file extension of the Go generated files
39 constModuleName = "ModuleName" // module name constant
40 constAPIVersion = "APIVersion" // API version constant
41 constVersionCrc = "VersionCrc" // version CRC constant
43 unionDataField = "XXX_UnionData" // name for the union data field
45 serviceApiName = "RPCService" // name for the RPC service interface
46 serviceImplName = "serviceClient" // name for the RPC service implementation
47 serviceClientName = "ServiceClient" // name for the RPC service client
50 // context is a structure storing data for code generation
52 inputFile string // input file with VPP API in JSON
53 outputFile string // output file with generated Go package
55 importPrefix string // defines import path prefix for importing types
57 inputData []byte // contents of the input file
59 includeAPIVersion bool // include constant with API version string
60 includeComments bool // include parts of original source in comments
61 includeBinapiNames bool // include binary API names as struct tag
62 includeServices bool // include service interface with client implementation
64 moduleName string // name of the source VPP module
65 packageName string // name of the Go package being generated
67 packageData *Package // parsed package data
70 // newContext returns context details of the code generation task
71 func newContext(inputFile, outputDir string) (*context, error) {
72 if !strings.HasSuffix(inputFile, inputFileExt) {
73 return nil, fmt.Errorf("invalid input file name: %q", inputFile)
81 inputFileName := filepath.Base(inputFile)
82 ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
84 // alter package names for modules that are reserved keywords in Go
85 switch ctx.moduleName {
87 ctx.packageName = "interfaces"
89 ctx.packageName = "maps"
91 ctx.packageName = ctx.moduleName
95 packageDir := filepath.Join(outputDir, ctx.packageName)
96 outputFileName := ctx.packageName + outputFileExt
97 ctx.outputFile = filepath.Join(packageDir, outputFileName)
102 func generatePackage(ctx *context, w io.Writer) error {
103 logf("----------------------------")
104 logf("generating package %q", ctx.packageName)
105 logf("----------------------------")
107 fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
108 fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
111 generateHeader(ctx, w)
112 generateImports(ctx, w)
114 // generate module desc
115 fmt.Fprintln(w, "const (")
116 fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
117 fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName)
119 if ctx.includeAPIVersion {
120 if ctx.packageData.Version != "" {
121 fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
122 fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
124 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
125 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
131 if len(ctx.packageData.Enums) > 0 {
132 for _, enum := range ctx.packageData.Enums {
133 if imp, ok := ctx.packageData.Imports[enum.Name]; ok {
134 generateImportedAlias(ctx, w, enum.Name, &imp)
137 generateEnum(ctx, w, &enum)
142 if len(ctx.packageData.Aliases) > 0 {
143 for _, alias := range ctx.packageData.Aliases {
144 if imp, ok := ctx.packageData.Imports[alias.Name]; ok {
145 generateImportedAlias(ctx, w, alias.Name, &imp)
148 generateAlias(ctx, w, &alias)
153 if len(ctx.packageData.Types) > 0 {
154 for _, typ := range ctx.packageData.Types {
155 if imp, ok := ctx.packageData.Imports[typ.Name]; ok {
156 generateImportedAlias(ctx, w, typ.Name, &imp)
159 generateType(ctx, w, &typ)
164 if len(ctx.packageData.Unions) > 0 {
165 for _, union := range ctx.packageData.Unions {
166 if imp, ok := ctx.packageData.Imports[union.Name]; ok {
167 generateImportedAlias(ctx, w, union.Name, &imp)
170 generateUnion(ctx, w, &union)
175 if len(ctx.packageData.Messages) > 0 {
176 for _, msg := range ctx.packageData.Messages {
177 generateMessage(ctx, w, &msg)
180 // generate message registrations
181 fmt.Fprintln(w, "func init() {")
182 for _, msg := range ctx.packageData.Messages {
183 name := camelCaseName(msg.Name)
184 fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
189 // generate list of messages
190 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
191 fmt.Fprintln(w, "func AllMessages() []api.Message {")
192 fmt.Fprintln(w, "\treturn []api.Message{")
193 for _, msg := range ctx.packageData.Messages {
194 name := camelCaseName(msg.Name)
195 fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
201 if ctx.includeServices {
203 if len(ctx.packageData.Services) > 0 {
204 generateServices(ctx, w, ctx.packageData.Services)
208 generateFooter(ctx, w)
213 func generateHeader(ctx *context, w io.Writer) {
214 fmt.Fprintln(w, "/*")
215 fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
217 fmt.Fprintln(w, "It consists of:")
218 printObjNum := func(obj string, num int) {
221 if strings.HasSuffix(obj, "s") {
228 fmt.Fprintf(w, "\t%3d %s\n", num, obj)
231 printObjNum("enum", len(ctx.packageData.Enums))
232 printObjNum("alias", len(ctx.packageData.Aliases))
233 printObjNum("type", len(ctx.packageData.Types))
234 printObjNum("union", len(ctx.packageData.Unions))
235 printObjNum("message", len(ctx.packageData.Messages))
236 printObjNum("service", len(ctx.packageData.Services))
237 fmt.Fprintln(w, "*/")
238 fmt.Fprintf(w, "package %s\n", ctx.packageName)
243 func generateImports(ctx *context, w io.Writer) {
244 fmt.Fprintln(w, "import (")
245 fmt.Fprintln(w, ` "bytes"`)
246 fmt.Fprintln(w, ` "context"`)
247 fmt.Fprintln(w, ` "io"`)
248 fmt.Fprintln(w, ` "strconv"`)
250 fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
251 fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
252 if len(ctx.packageData.Imports) > 0 {
254 for _, imp := range getImports(ctx) {
255 importPath := path.Join(ctx.importPrefix, imp)
256 if importPath == "" {
257 importPath = getImportPath(filepath.Dir(ctx.outputFile), imp)
259 fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
266 func getImportPath(outputDir string, pkg string) string {
267 absPath, _ := filepath.Abs(filepath.Join(outputDir, "..", pkg))
268 cmd := exec.Command("go", "list", absPath)
269 var errbuf, outbuf bytes.Buffer
272 if err := cmd.Run(); err != nil {
273 fmt.Printf("ERR: %v\n", errbuf.String())
276 return outbuf.String()
279 func getImports(ctx *context) (imports []string) {
280 impmap := map[string]struct{}{}
281 for _, imp := range ctx.packageData.Imports {
282 if _, ok := impmap[imp.Package]; !ok {
283 imports = append(imports, imp.Package)
284 impmap[imp.Package] = struct{}{}
287 sort.Strings(imports)
291 func generateFooter(ctx *context, w io.Writer) {
292 fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
293 fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
294 fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
295 fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
296 fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
299 fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
300 fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
301 fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
302 fmt.Fprintf(w, "var _ = context.Background\n")
303 fmt.Fprintf(w, "var _ = io.Copy\n")
304 fmt.Fprintf(w, "var _ = strconv.Itoa\n")
305 fmt.Fprintf(w, "var _ = struc.Pack\n")
308 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
309 if objKind == "service" {
310 fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
312 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
315 if !ctx.includeComments {
319 var isNotSpace = func(r rune) bool {
320 return !unicode.IsSpace(r)
323 // print out the source of the generated object
326 objTitle := fmt.Sprintf(`"%s",`, vppName)
328 case "alias", "service":
329 objTitle = fmt.Sprintf(`"%s": {`, vppName)
333 inputBuff := bytes.NewBuffer(ctx.inputData)
336 var trimIndent string
339 line, err := inputBuff.ReadString('\n')
345 noSpaceAt := strings.IndexFunc(line, isNotSpace)
347 indent = strings.Index(line, objTitle)
351 trimIndent = line[:indent]
352 // If no other non-whitespace character then we are at the message header.
353 if trimmed := strings.TrimSpace(line); trimmed == objTitle {
355 fmt.Fprintln(w, "//")
357 } else if noSpaceAt < indent {
358 break // end of the definition in JSON for array types
359 } else if objFound && mapType && noSpaceAt <= indent {
360 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
361 break // end of the definition in JSON for map types (aliases, services)
363 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
366 fmt.Fprintln(w, "//")
369 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
370 name := camelCaseName(enum.Name)
371 typ := binapiTypes[enum.Type]
373 logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
375 // generate enum comment
376 generateComment(ctx, w, name, enum.Name, "enum")
378 // generate enum definition
379 fmt.Fprintf(w, "type %s %s\n", name, typ)
382 // generate enum entries
383 fmt.Fprintln(w, "const (")
384 for _, entry := range enum.Entries {
385 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
390 // generate enum conversion maps
391 fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
392 for _, entry := range enum.Entries {
393 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
398 fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
399 for _, entry := range enum.Entries {
400 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
405 fmt.Fprintf(w, "func (x %s) String() string {\n", name)
406 fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
407 fmt.Fprintf(w, "\tif ok { return s }\n")
408 fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
413 func generateImportedAlias(ctx *context, w io.Writer, tName string, imp *Import) {
414 name := camelCaseName(tName)
416 fmt.Fprintf(w, "type %s = %s.%s\n", name, imp.Package, name)
421 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
422 name := camelCaseName(alias.Name)
424 logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
426 // generate struct comment
427 generateComment(ctx, w, name, alias.Name, "alias")
429 // generate struct definition
430 fmt.Fprintf(w, "type %s ", name)
432 if alias.Length > 0 {
433 fmt.Fprintf(w, "[%d]", alias.Length)
436 dataType := convertToGoType(ctx, alias.Type)
437 fmt.Fprintf(w, "%s\n", dataType)
442 func generateUnion(ctx *context, w io.Writer, union *Union) {
443 name := camelCaseName(union.Name)
445 logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
447 // generate struct comment
448 generateComment(ctx, w, name, union.Name, "union")
450 // generate struct definition
451 fmt.Fprintln(w, "type", name, "struct {")
453 // maximum size for union
454 maxSize := getUnionSize(ctx, union)
456 // generate data field
457 fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
459 // generate end of the struct
462 // generate name getter
463 generateTypeNameGetter(w, name, union.Name)
465 // generate CRC getter
467 generateCrcGetter(w, name, union.CRC)
470 // generate getters for fields
471 for _, field := range union.Fields {
472 fieldName := camelCaseName(field.Name)
473 fieldType := convertToGoType(ctx, field.Type)
474 generateUnionGetterSetter(w, name, fieldName, fieldType)
477 // generate union methods
478 //generateUnionMethods(w, name)
483 // generateUnionMethods generates methods that implement struc.Custom
484 // interface to allow having XXX_uniondata field unexported
485 // TODO: do more testing when unions are actually used in some messages
486 /*func generateUnionMethods(w io.Writer, structName string) {
487 // generate struc.Custom implementation for union
489 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
490 var b = new(bytes.Buffer)
491 if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
497 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
498 return struc.UnpackWithOptions(r, u.union_data[:], opt)
500 func (u *%[1]s) Size(opt *struc.Options) int {
501 return len(u.union_data)
503 func (u *%[1]s) String() string {
504 return string(u.union_data[:])
509 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
511 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
515 func (u *%[1]s) Set%[2]s(a %[3]s) {
516 var b = new(bytes.Buffer)
517 if err := struc.Pack(b, &a); err != nil {
520 copy(u.%[4]s[:], b.Bytes())
522 func (u *%[1]s) Get%[2]s() (a %[3]s) {
523 var b = bytes.NewReader(u.%[4]s[:])
527 `, structName, getterField, getterStruct, unionDataField)
530 func generateType(ctx *context, w io.Writer, typ *Type) {
531 name := camelCaseName(typ.Name)
533 logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
535 // generate struct comment
536 generateComment(ctx, w, name, typ.Name, "type")
538 // generate struct definition
539 fmt.Fprintf(w, "type %s struct {\n", name)
541 // generate struct fields
542 for i, field := range typ.Fields {
543 // skip internal fields
544 switch strings.ToLower(field.Name) {
545 case crcField, msgIdField:
549 generateField(ctx, w, typ.Fields, i)
552 // generate end of the struct
555 // generate name getter
556 generateTypeNameGetter(w, name, typ.Name)
558 // generate CRC getter
560 generateCrcGetter(w, name, typ.CRC)
566 func generateMessage(ctx *context, w io.Writer, msg *Message) {
567 name := camelCaseName(msg.Name)
569 logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
571 // generate struct comment
572 generateComment(ctx, w, name, msg.Name, "message")
574 // generate struct definition
575 fmt.Fprintf(w, "type %s struct {", name)
577 msgType := otherMessage
578 wasClientIndex := false
580 // generate struct fields
582 for i, field := range msg.Fields {
584 if field.Name == clientIndexField {
585 // "client_index" as the second member,
586 // this might be an event message or a request
587 msgType = eventMessage
588 wasClientIndex = true
589 } else if field.Name == contextField {
590 // reply needs "context" as the second member
591 msgType = replyMessage
594 if wasClientIndex && field.Name == contextField {
595 // request needs "client_index" as the second member
596 // and "context" as the third member
597 msgType = requestMessage
601 // skip internal fields
602 switch strings.ToLower(field.Name) {
603 case crcField, msgIdField:
605 case clientIndexField, contextField:
615 generateField(ctx, w, msg.Fields, i)
618 // generate end of the struct
621 // generate message methods
622 generateMessageResetMethod(w, name)
623 generateMessageNameGetter(w, name, msg.Name)
624 generateCrcGetter(w, name, msg.CRC)
625 generateMessageTypeGetter(w, name, msgType)
630 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
633 fieldName := strings.TrimPrefix(field.Name, "_")
634 fieldName = camelCaseName(fieldName)
636 dataType := convertToGoType(ctx, field.Type)
637 fieldType := dataType
639 // generate length field for strings
640 if field.Type == "string" && field.Length == 0 {
641 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
644 // check if it is array
645 if field.Length > 0 || field.SizeFrom != "" {
646 if dataType == "uint8" {
649 if dataType == "string" && field.SpecifiedLen {
653 fieldType = "[]" + dataType
656 fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
658 fieldTags := map[string]string{}
660 if field.Length > 0 && field.SpecifiedLen {
662 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
664 for _, f := range fields {
665 if f.SizeFrom == field.Name {
666 // variable sized array
667 sizeOfName := camelCaseName(f.Name)
668 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
673 if ctx.includeBinapiNames {
674 fieldTags["binapi"] = field.Name
676 if field.Meta.Limit > 0 {
677 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
680 if len(fieldTags) > 0 {
681 fmt.Fprintf(w, "\t`")
683 for k := range fieldTags {
684 keys = append(keys, k)
688 for _, tt := range keys {
689 t, ok := fieldTags[tt]
697 fmt.Fprintf(w, `%s:"%s"`, tt, t)
705 func generateMessageResetMethod(w io.Writer, structName string) {
706 fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
709 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
710 fmt.Fprintf(w, "func (*%s) GetMessageName() string { return %q }\n", structName, msgName)
713 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
714 fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName)
717 func generateCrcGetter(w io.Writer, structName, crc string) {
718 crc = strings.TrimPrefix(crc, "0x")
719 fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc)
722 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
723 fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {")
724 if msgType == requestMessage {
725 fmt.Fprintf(w, "\treturn api.RequestMessage")
726 } else if msgType == replyMessage {
727 fmt.Fprintf(w, "\treturn api.ReplyMessage")
728 } else if msgType == eventMessage {
729 fmt.Fprintf(w, "\treturn api.EventMessage")
731 fmt.Fprintf(w, "\treturn api.OtherMessage")
737 func generateServices(ctx *context, w io.Writer, services []Service) {
739 // generate services comment
740 generateComment(ctx, w, serviceApiName, "services", "service")
742 // generate service api
743 fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
744 for _, svc := range services {
745 generateServiceMethod(ctx, w, &svc)
751 // generate client implementation
752 fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
753 fmt.Fprintf(w, "\tch api.Channel\n")
757 // generate client constructor
758 fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
759 fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
763 for _, svc := range services {
764 method := camelCaseName(svc.RequestType)
765 if m := strings.TrimSuffix(method, "Dump"); method != m {
769 fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
770 generateServiceMethod(ctx, w, &svc)
771 fmt.Fprintln(w, " {")
773 streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
774 fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
775 fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
776 fmt.Fprintf(w, "\treturn x, nil\n")
777 } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
778 fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
779 fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
780 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
781 fmt.Fprintf(w, "\treturn out, nil\n")
783 fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
784 fmt.Fprintf(w, "\treturn nil\n")
790 replyTyp := camelCaseName(svc.ReplyType)
791 method := camelCaseName(svc.RequestType)
792 if m := strings.TrimSuffix(method, "Dump"); method != m {
795 streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
797 fmt.Fprintf(w, "type %s interface {\n", streamApi)
798 fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
802 streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
803 fmt.Fprintf(w, "type %s struct {\n", streamImpl)
804 fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
808 fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
809 fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
810 fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
811 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
812 fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
813 fmt.Fprintf(w, "\treturn m, nil\n")
822 func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
823 reqTyp := camelCaseName(svc.RequestType)
825 // method name is same as parameter type name by default
828 // use Dump as prefix instead of suffix for stream services
829 if m := strings.TrimSuffix(method, "Dump"); method != m {
834 params := fmt.Sprintf("in *%s", reqTyp)
837 if replyType := camelCaseName(svc.ReplyType); replyType != "" {
840 replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
842 replyTyp = fmt.Sprintf("*%s", replyType)
844 returns = fmt.Sprintf("(%s, error)", replyTyp)
847 fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)