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 govppApiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API package
41 constModuleName = "ModuleName" // module name constant
42 constAPIVersion = "APIVersion" // API version constant
43 constVersionCrc = "VersionCrc" // version CRC constant
45 unionDataField = "XXX_UnionData" // name for the union data field
47 serviceApiName = "RPCService" // name for the RPC service interface
48 serviceImplName = "serviceClient" // name for the RPC service implementation
49 serviceClientName = "ServiceClient" // name for the RPC service client
52 // context is a structure storing data for code generation
54 inputFile string // input file with VPP API in JSON
55 outputFile string // output file with generated Go package
57 importPrefix string // defines import path prefix for importing types
59 inputData []byte // contents of the input file
61 includeAPIVersion bool // include constant with API version string
62 includeComments bool // include parts of original source in comments
63 includeBinapiNames bool // include binary API names as struct tag
64 includeServices bool // include service interface with client implementation
66 moduleName string // name of the source VPP module
67 packageName string // name of the Go package being generated
69 packageData *Package // parsed package data
72 // newContext returns context details of the code generation task
73 func newContext(inputFile, outputDir string) (*context, error) {
74 if !strings.HasSuffix(inputFile, inputFileExt) {
75 return nil, fmt.Errorf("invalid input file name: %q", inputFile)
83 inputFileName := filepath.Base(inputFile)
84 ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
86 // alter package names for modules that are reserved keywords in Go
87 switch ctx.moduleName {
89 ctx.packageName = "interfaces"
91 ctx.packageName = "maps"
93 ctx.packageName = ctx.moduleName
97 packageDir := filepath.Join(outputDir, ctx.packageName)
98 outputFileName := ctx.packageName + outputFileExt
99 ctx.outputFile = filepath.Join(packageDir, outputFileName)
104 func generatePackage(ctx *context, w io.Writer) error {
105 logf("----------------------------")
106 logf("generating package %q", ctx.packageName)
107 logf("----------------------------")
109 fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
110 fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
113 generateHeader(ctx, w)
114 generateImports(ctx, w)
116 // generate module desc
117 fmt.Fprintln(w, "const (")
118 fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
119 fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName)
121 if ctx.includeAPIVersion {
122 if ctx.packageData.Version != "" {
123 fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
124 fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
126 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
127 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
133 if len(ctx.packageData.Enums) > 0 {
134 for _, enum := range ctx.packageData.Enums {
135 if imp, ok := ctx.packageData.Imports[enum.Name]; ok {
136 generateImportedAlias(ctx, w, enum.Name, &imp)
139 generateEnum(ctx, w, &enum)
144 if len(ctx.packageData.Aliases) > 0 {
145 for _, alias := range ctx.packageData.Aliases {
146 if imp, ok := ctx.packageData.Imports[alias.Name]; ok {
147 generateImportedAlias(ctx, w, alias.Name, &imp)
150 generateAlias(ctx, w, &alias)
155 if len(ctx.packageData.Types) > 0 {
156 for _, typ := range ctx.packageData.Types {
157 if imp, ok := ctx.packageData.Imports[typ.Name]; ok {
158 generateImportedAlias(ctx, w, typ.Name, &imp)
161 generateType(ctx, w, &typ)
166 if len(ctx.packageData.Unions) > 0 {
167 for _, union := range ctx.packageData.Unions {
168 if imp, ok := ctx.packageData.Imports[union.Name]; ok {
169 generateImportedAlias(ctx, w, union.Name, &imp)
172 generateUnion(ctx, w, &union)
177 if len(ctx.packageData.Messages) > 0 {
178 for _, msg := range ctx.packageData.Messages {
179 generateMessage(ctx, w, &msg)
182 // generate message registrations
183 fmt.Fprintln(w, "func init() {")
184 for _, msg := range ctx.packageData.Messages {
185 name := camelCaseName(msg.Name)
186 fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
191 // generate list of messages
192 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
193 fmt.Fprintln(w, "func AllMessages() []api.Message {")
194 fmt.Fprintln(w, "\treturn []api.Message{")
195 for _, msg := range ctx.packageData.Messages {
196 name := camelCaseName(msg.Name)
197 fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
203 if ctx.includeServices {
205 if len(ctx.packageData.Services) > 0 {
206 generateServices(ctx, w, ctx.packageData.Services)
210 generateFooter(ctx, w)
215 func generateHeader(ctx *context, w io.Writer) {
216 fmt.Fprintln(w, "/*")
217 fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
219 fmt.Fprintln(w, "It consists of:")
220 printObjNum := func(obj string, num int) {
223 if strings.HasSuffix(obj, "s") {
230 fmt.Fprintf(w, "\t%3d %s\n", num, obj)
233 printObjNum("enum", len(ctx.packageData.Enums))
234 printObjNum("alias", len(ctx.packageData.Aliases))
235 printObjNum("type", len(ctx.packageData.Types))
236 printObjNum("union", len(ctx.packageData.Unions))
237 printObjNum("message", len(ctx.packageData.Messages))
238 printObjNum("service", len(ctx.packageData.Services))
239 fmt.Fprintln(w, "*/")
240 fmt.Fprintf(w, "package %s\n", ctx.packageName)
245 func generateImports(ctx *context, w io.Writer) {
246 fmt.Fprintln(w, "import (")
247 fmt.Fprintf(w, "\tapi \"%s\"\n", govppApiImportPath)
248 fmt.Fprintf(w, "\tbytes \"%s\"\n", "bytes")
249 fmt.Fprintf(w, "\tcontext \"%s\"\n", "context")
250 fmt.Fprintf(w, "\tio \"%s\"\n", "io")
251 fmt.Fprintf(w, "\tstrconv \"%s\"\n", "strconv")
252 fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
253 if len(ctx.packageData.Imports) > 0 {
255 for _, imp := range getImports(ctx) {
256 importPath := path.Join(ctx.importPrefix, imp)
257 if importPath == "" {
258 importPath = getImportPath(filepath.Dir(ctx.outputFile), imp)
260 fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
267 func getImportPath(outputDir string, pkg string) string {
268 absPath, _ := filepath.Abs(filepath.Join(outputDir, "..", pkg))
269 cmd := exec.Command("go", "list", absPath)
270 var errbuf, outbuf bytes.Buffer
273 if err := cmd.Run(); err != nil {
274 fmt.Printf("ERR: %v\n", errbuf.String())
277 return outbuf.String()
280 func getImports(ctx *context) (imports []string) {
281 impmap := map[string]struct{}{}
282 for _, imp := range ctx.packageData.Imports {
283 if _, ok := impmap[imp.Package]; !ok {
284 imports = append(imports, imp.Package)
285 impmap[imp.Package] = struct{}{}
288 sort.Strings(imports)
292 func generateFooter(ctx *context, w io.Writer) {
293 fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
294 fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
295 fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
296 fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
297 fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
300 fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
301 fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
302 fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
303 fmt.Fprintf(w, "var _ = context.Background\n")
304 fmt.Fprintf(w, "var _ = io.Copy\n")
305 fmt.Fprintf(w, "var _ = strconv.Itoa\n")
306 fmt.Fprintf(w, "var _ = struc.Pack\n")
309 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
310 if objKind == "service" {
311 fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
313 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
316 if !ctx.includeComments {
320 var isNotSpace = func(r rune) bool {
321 return !unicode.IsSpace(r)
324 // print out the source of the generated object
327 objTitle := fmt.Sprintf(`"%s",`, vppName)
329 case "alias", "service":
330 objTitle = fmt.Sprintf(`"%s": {`, vppName)
334 inputBuff := bytes.NewBuffer(ctx.inputData)
337 var trimIndent string
340 line, err := inputBuff.ReadString('\n')
346 noSpaceAt := strings.IndexFunc(line, isNotSpace)
348 indent = strings.Index(line, objTitle)
352 trimIndent = line[:indent]
353 // If no other non-whitespace character then we are at the message header.
354 if trimmed := strings.TrimSpace(line); trimmed == objTitle {
356 fmt.Fprintln(w, "//")
358 } else if noSpaceAt < indent {
359 break // end of the definition in JSON for array types
360 } else if objFound && mapType && noSpaceAt <= indent {
361 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
362 break // end of the definition in JSON for map types (aliases, services)
364 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
367 fmt.Fprintln(w, "//")
370 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
371 name := camelCaseName(enum.Name)
372 typ := binapiTypes[enum.Type]
374 logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
376 // generate enum comment
377 generateComment(ctx, w, name, enum.Name, "enum")
379 // generate enum definition
380 fmt.Fprintf(w, "type %s %s\n", name, typ)
383 // generate enum entries
384 fmt.Fprintln(w, "const (")
385 for _, entry := range enum.Entries {
386 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
391 // generate enum conversion maps
392 fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
393 for _, entry := range enum.Entries {
394 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
399 fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
400 for _, entry := range enum.Entries {
401 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
406 fmt.Fprintf(w, "func (x %s) String() string {\n", name)
407 fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
408 fmt.Fprintf(w, "\tif ok { return s }\n")
409 fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
414 func generateImportedAlias(ctx *context, w io.Writer, tName string, imp *Import) {
415 name := camelCaseName(tName)
417 fmt.Fprintf(w, "type %s = %s.%s\n", name, imp.Package, name)
422 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
423 name := camelCaseName(alias.Name)
425 logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
427 // generate struct comment
428 generateComment(ctx, w, name, alias.Name, "alias")
430 // generate struct definition
431 fmt.Fprintf(w, "type %s ", name)
433 if alias.Length > 0 {
434 fmt.Fprintf(w, "[%d]", alias.Length)
437 dataType := convertToGoType(ctx, alias.Type)
438 fmt.Fprintf(w, "%s\n", dataType)
443 func generateUnion(ctx *context, w io.Writer, union *Union) {
444 name := camelCaseName(union.Name)
446 logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
448 // generate struct comment
449 generateComment(ctx, w, name, union.Name, "union")
451 // generate struct definition
452 fmt.Fprintln(w, "type", name, "struct {")
454 // maximum size for union
455 maxSize := getUnionSize(ctx, union)
457 // generate data field
458 fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
460 // generate end of the struct
463 // generate name getter
464 generateTypeNameGetter(w, name, union.Name)
466 // generate CRC getter
468 generateCrcGetter(w, name, union.CRC)
471 // generate getters for fields
472 for _, field := range union.Fields {
473 fieldName := camelCaseName(field.Name)
474 fieldType := convertToGoType(ctx, field.Type)
475 generateUnionGetterSetter(w, name, fieldName, fieldType)
478 // generate union methods
479 //generateUnionMethods(w, name)
484 // generateUnionMethods generates methods that implement struc.Custom
485 // interface to allow having XXX_uniondata field unexported
486 // TODO: do more testing when unions are actually used in some messages
487 /*func generateUnionMethods(w io.Writer, structName string) {
488 // generate struc.Custom implementation for union
490 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
491 var b = new(bytes.Buffer)
492 if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
498 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
499 return struc.UnpackWithOptions(r, u.union_data[:], opt)
501 func (u *%[1]s) Size(opt *struc.Options) int {
502 return len(u.union_data)
504 func (u *%[1]s) String() string {
505 return string(u.union_data[:])
510 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
512 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
516 func (u *%[1]s) Set%[2]s(a %[3]s) {
517 var b = new(bytes.Buffer)
518 if err := struc.Pack(b, &a); err != nil {
521 copy(u.%[4]s[:], b.Bytes())
523 func (u *%[1]s) Get%[2]s() (a %[3]s) {
524 var b = bytes.NewReader(u.%[4]s[:])
528 `, structName, getterField, getterStruct, unionDataField)
531 func generateType(ctx *context, w io.Writer, typ *Type) {
532 name := camelCaseName(typ.Name)
534 logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
536 // generate struct comment
537 generateComment(ctx, w, name, typ.Name, "type")
539 // generate struct definition
540 fmt.Fprintf(w, "type %s struct {\n", name)
542 // generate struct fields
543 for i, field := range typ.Fields {
544 // skip internal fields
545 switch strings.ToLower(field.Name) {
546 case crcField, msgIdField:
550 generateField(ctx, w, typ.Fields, i)
553 // generate end of the struct
556 // generate name getter
557 generateTypeNameGetter(w, name, typ.Name)
559 // generate CRC getter
561 generateCrcGetter(w, name, typ.CRC)
567 func generateMessage(ctx *context, w io.Writer, msg *Message) {
568 name := camelCaseName(msg.Name)
570 logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
572 // generate struct comment
573 generateComment(ctx, w, name, msg.Name, "message")
575 // generate struct definition
576 fmt.Fprintf(w, "type %s struct {", name)
578 msgType := otherMessage
579 wasClientIndex := false
581 // generate struct fields
583 for i, field := range msg.Fields {
585 if field.Name == clientIndexField {
586 // "client_index" as the second member,
587 // this might be an event message or a request
588 msgType = eventMessage
589 wasClientIndex = true
590 } else if field.Name == contextField {
591 // reply needs "context" as the second member
592 msgType = replyMessage
595 if wasClientIndex && field.Name == contextField {
596 // request needs "client_index" as the second member
597 // and "context" as the third member
598 msgType = requestMessage
602 // skip internal fields
603 switch strings.ToLower(field.Name) {
604 case crcField, msgIdField:
606 case clientIndexField, contextField:
616 generateField(ctx, w, msg.Fields, i)
619 // generate end of the struct
622 // generate message methods
623 generateMessageResetMethod(w, name)
624 generateMessageNameGetter(w, name, msg.Name)
625 generateCrcGetter(w, name, msg.CRC)
626 generateMessageTypeGetter(w, name, msgType)
631 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
634 fieldName := strings.TrimPrefix(field.Name, "_")
635 fieldName = camelCaseName(fieldName)
637 dataType := convertToGoType(ctx, field.Type)
638 fieldType := dataType
640 // generate length field for strings
641 if field.Type == "string" && field.Length == 0 {
642 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
645 // check if it is array
646 if field.Length > 0 || field.SizeFrom != "" {
647 if dataType == "uint8" {
650 if dataType == "string" && field.SpecifiedLen {
654 fieldType = "[]" + dataType
657 fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
659 fieldTags := map[string]string{}
661 if field.Length > 0 && field.SpecifiedLen {
663 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
665 for _, f := range fields {
666 if f.SizeFrom == field.Name {
667 // variable sized array
668 sizeOfName := camelCaseName(f.Name)
669 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
674 if ctx.includeBinapiNames {
675 fieldTags["binapi"] = field.Name
677 if field.Meta.Limit > 0 {
678 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
681 if len(fieldTags) > 0 {
682 fmt.Fprintf(w, "\t`")
684 for k := range fieldTags {
685 keys = append(keys, k)
689 for _, tt := range keys {
690 t, ok := fieldTags[tt]
698 fmt.Fprintf(w, `%s:"%s"`, tt, t)
706 func generateMessageResetMethod(w io.Writer, structName string) {
707 fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
710 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
711 fmt.Fprintf(w, "func (*%s) GetMessageName() string { return %q }\n", structName, msgName)
714 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
715 fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName)
718 func generateCrcGetter(w io.Writer, structName, crc string) {
719 crc = strings.TrimPrefix(crc, "0x")
720 fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc)
723 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
724 fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {")
725 if msgType == requestMessage {
726 fmt.Fprintf(w, "\treturn api.RequestMessage")
727 } else if msgType == replyMessage {
728 fmt.Fprintf(w, "\treturn api.ReplyMessage")
729 } else if msgType == eventMessage {
730 fmt.Fprintf(w, "\treturn api.EventMessage")
732 fmt.Fprintf(w, "\treturn api.OtherMessage")
738 func generateServices(ctx *context, w io.Writer, services []Service) {
740 // generate services comment
741 generateComment(ctx, w, serviceApiName, "services", "service")
743 // generate service api
744 fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
745 for _, svc := range services {
746 generateServiceMethod(ctx, w, &svc)
752 // generate client implementation
753 fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
754 fmt.Fprintf(w, "\tch api.Channel\n")
758 // generate client constructor
759 fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
760 fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
764 for _, svc := range services {
765 method := camelCaseName(svc.RequestType)
766 if m := strings.TrimSuffix(method, "Dump"); method != m {
770 fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
771 generateServiceMethod(ctx, w, &svc)
772 fmt.Fprintln(w, " {")
774 streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
775 fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
776 fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
777 fmt.Fprintf(w, "\treturn x, nil\n")
778 } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
779 fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
780 fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
781 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
782 fmt.Fprintf(w, "\treturn out, nil\n")
784 fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
785 fmt.Fprintf(w, "\treturn nil\n")
791 replyTyp := camelCaseName(svc.ReplyType)
792 method := camelCaseName(svc.RequestType)
793 if m := strings.TrimSuffix(method, "Dump"); method != m {
796 streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
798 fmt.Fprintf(w, "type %s interface {\n", streamApi)
799 fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
803 streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
804 fmt.Fprintf(w, "type %s struct {\n", streamImpl)
805 fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
809 fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
810 fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
811 fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
812 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
813 fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
814 fmt.Fprintf(w, "\treturn m, nil\n")
823 func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
824 reqTyp := camelCaseName(svc.RequestType)
826 // method name is same as parameter type name by default
829 // use Dump as prefix instead of suffix for stream services
830 if m := strings.TrimSuffix(method, "Dump"); method != m {
835 params := fmt.Sprintf("in *%s", reqTyp)
838 if replyType := camelCaseName(svc.ReplyType); replyType != "" {
841 replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
843 replyTyp = fmt.Sprintf("*%s", replyType)
845 returns = fmt.Sprintf("(%s, error)", replyTyp)
848 fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)