// Copyright (c) 2017 Cisco and/or its affiliates. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "fmt" "io" "os/exec" "path/filepath" "sort" "strings" "unicode" ) // generatedCodeVersion indicates a version of the generated code. // It is incremented whenever an incompatibility between the generated code and // GoVPP api package is introduced; the generated code references // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion). const generatedCodeVersion = 1 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 type context struct { inputFile string // input file with VPP API in JSON outputFile string // output file with generated Go package inputData []byte // contents of the input file includeAPIVersion bool // include constant with API version string includeComments bool // include parts of original source in comments includeBinapiNames bool // include binary API names as struct tag includeServices bool // include service interface with client implementation moduleName string // name of the source VPP module packageName string // name of the Go package being generated packageData *Package // parsed package data } // 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) } ctx := &context{ inputFile: inputFile, } // package name inputFileName := filepath.Base(inputFile) ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")] // alter package names for modules that are reserved keywords in Go switch ctx.moduleName { case "interface": ctx.packageName = "interfaces" case "map": ctx.packageName = "maps" default: ctx.packageName = ctx.moduleName } // output file packageDir := filepath.Join(outputDir, ctx.packageName) outputFileName := ctx.packageName + outputFileExt ctx.outputFile = filepath.Join(packageDir, outputFileName) return ctx, nil } func generatePackage(ctx *context, w io.Writer) error { logf("generating package %q", ctx.packageName) 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) // generate module desc fmt.Fprintln(w, "const (") fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName) fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName) if ctx.includeAPIVersion { if ctx.packageData.Version != "" { fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion) fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version) } fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc) fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC) } fmt.Fprintln(w, ")") fmt.Fprintln(w) // generate enums if len(ctx.packageData.Enums) > 0 { 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 { 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 { 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 { 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 { for _, msg := range ctx.packageData.Messages { generateMessage(ctx, w, &msg) } // generate message registrations fmt.Fprintln(w, "func init() {") for _, msg := range ctx.packageData.Messages { name := camelCaseName(msg.Name) fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name) } fmt.Fprintln(w, "}") fmt.Fprintln(w) // generate list of messages fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n") fmt.Fprintln(w, "func AllMessages() []api.Message {") fmt.Fprintln(w, "\treturn []api.Message{") for _, msg := range ctx.packageData.Messages { name := camelCaseName(msg.Name) fmt.Fprintf(w, "\t(*%s)(nil),\n", name) } fmt.Fprintln(w, "}") fmt.Fprintln(w, "}") } if ctx.includeServices { // generate services if len(ctx.packageData.Services) > 0 { generateServices(ctx, w, ctx.packageData.Services) } } generateFooter(ctx, w) return nil } func generateHeader(ctx *context, w io.Writer) { fmt.Fprintln(w, "/*") fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName) fmt.Fprintln(w) fmt.Fprintln(w, "It consists of:") printObjNum := func(obj string, num int) { if num > 0 { if num > 1 { if strings.HasSuffix(obj, "s") { obj += "es" } else { obj += "s" } } 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)) printObjNum("union", len(ctx.packageData.Unions)) 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") if len(ctx.packageData.Imports) > 0 { fmt.Fprintln(w) for _, imp := range getImports(ctx) { impPkg := getImportPkg(filepath.Dir(ctx.outputFile), imp) fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(impPkg)) } } fmt.Fprintln(w, ")") fmt.Fprintln(w) } func getImportPkg(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") } func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) { if objKind == "service" { 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) } if !ctx.includeComments { return } var isNotSpace = func(r rune) bool { return !unicode.IsSpace(r) } // print out the source of the generated object mapType := false objFound := false objTitle := fmt.Sprintf(`"%s",`, vppName) switch objKind { case "alias", "service": objTitle = fmt.Sprintf(`"%s": {`, vppName) mapType = true } inputBuff := bytes.NewBuffer(ctx.inputData) inputLine := 0 var trimIndent string var indent int for { line, err := inputBuff.ReadString('\n') if err != nil { break } inputLine++ noSpaceAt := strings.IndexFunc(line, isNotSpace) if !objFound { indent = strings.Index(line, objTitle) if indent == -1 { continue } trimIndent = line[:indent] // If no other non-whitespace character then we are at the message header. if trimmed := strings.TrimSpace(line); trimmed == objTitle { objFound = true fmt.Fprintln(w, "//") } } else if noSpaceAt < indent { break // end of the definition in JSON for array types } else if objFound && mapType && noSpaceAt <= indent { fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent)) break // end of the definition in JSON for map types (aliases, services) } fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent)) } fmt.Fprintln(w, "//") } func generateEnum(ctx *context, w io.Writer, enum *Enum) { name := camelCaseName(enum.Name) typ := binapiTypes[enum.Type] logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries)) // generate enum comment generateComment(ctx, w, name, enum.Name, "enum") // generate enum definition fmt.Fprintf(w, "type %s %s\n", name, typ) fmt.Fprintln(w) // generate enum entries fmt.Fprintln(w, "const (") for _, entry := range enum.Entries { fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value) } fmt.Fprintln(w, ")") fmt.Fprintln(w) // generate enum conversion maps fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ) for _, entry := range enum.Entries { fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name) } fmt.Fprintln(w, "}") fmt.Fprintln(w) fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ) for _, entry := range enum.Entries { fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value) } fmt.Fprintln(w, "}") fmt.Fprintln(w) fmt.Fprintf(w, "func (x %s) String() string {\n", name) fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ) fmt.Fprintf(w, "\tif ok { return s }\n") fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n") fmt.Fprintln(w, "}") fmt.Fprintln(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) logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length) // generate struct comment generateComment(ctx, w, name, alias.Name, "alias") // generate struct definition fmt.Fprintf(w, "type %s ", name) if alias.Length > 0 { fmt.Fprintf(w, "[%d]", alias.Length) } dataType := convertToGoType(ctx, alias.Type) fmt.Fprintf(w, "%s\n", dataType) fmt.Fprintln(w) } func generateUnion(ctx *context, w io.Writer, union *Union) { name := camelCaseName(union.Name) logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields)) // generate struct comment generateComment(ctx, w, name, union.Name, "union") // generate struct definition fmt.Fprintln(w, "type", name, "struct {") // maximum size for union maxSize := getUnionSize(ctx, union) // generate data field fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize) // generate end of the struct fmt.Fprintln(w, "}") // generate name getter generateTypeNameGetter(w, name, union.Name) // generate CRC getter if union.CRC != "" { generateCrcGetter(w, name, union.CRC) } // generate getters for fields for _, field := range union.Fields { fieldName := camelCaseName(field.Name) fieldType := convertToGoType(ctx, field.Type) generateUnionGetterSetter(w, name, fieldName, fieldType) } // generate union methods //generateUnionMethods(w, name) fmt.Fprintln(w) } // generateUnionMethods generates methods that implement struc.Custom // interface to allow having XXX_uniondata field unexported // TODO: do more testing when unions are actually used in some messages /*func generateUnionMethods(w io.Writer, structName string) { // generate struc.Custom implementation for union fmt.Fprintf(w, ` func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) { var b = new(bytes.Buffer) if err := struc.PackWithOptions(b, u.union_data, opt); err != nil { return 0, err } copy(p, b.Bytes()) return b.Len(), nil } func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error { return struc.UnpackWithOptions(r, u.union_data[:], opt) } func (u *%[1]s) Size(opt *struc.Options) int { return len(u.union_data) } func (u *%[1]s) String() string { return string(u.union_data[:]) } `, structName) }*/ func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) { fmt.Fprintf(w, ` func %[1]s%[2]s(a %[3]s) (u %[1]s) { u.Set%[2]s(a) return } func (u *%[1]s) Set%[2]s(a %[3]s) { var b = new(bytes.Buffer) if err := struc.Pack(b, &a); err != nil { return } copy(u.%[4]s[:], b.Bytes()) } func (u *%[1]s) Get%[2]s() (a %[3]s) { var b = bytes.NewReader(u.%[4]s[:]) struc.Unpack(b, &a) return } `, structName, getterField, getterStruct, unionDataField) } func generateType(ctx *context, w io.Writer, typ *Type) { name := camelCaseName(typ.Name) logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields)) // generate struct comment generateComment(ctx, w, name, typ.Name, "type") // generate struct definition fmt.Fprintf(w, "type %s struct {\n", name) // generate struct fields for i, field := range typ.Fields { // skip internal fields switch strings.ToLower(field.Name) { case crcField, msgIdField: continue } generateField(ctx, w, typ.Fields, i) } // generate end of the struct fmt.Fprintln(w, "}") // generate name getter generateTypeNameGetter(w, name, typ.Name) // generate CRC getter if typ.CRC != "" { generateCrcGetter(w, name, typ.CRC) } fmt.Fprintln(w) } func generateMessage(ctx *context, w io.Writer, msg *Message) { name := camelCaseName(msg.Name) logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields)) // generate struct comment generateComment(ctx, w, name, msg.Name, "message") // generate struct definition fmt.Fprintf(w, "type %s struct {", name) msgType := otherMessage wasClientIndex := false // generate struct fields n := 0 for i, field := range msg.Fields { if i == 1 { if field.Name == clientIndexField { // "client_index" as the second member, // this might be an event message or a request msgType = eventMessage wasClientIndex = true } else if field.Name == contextField { // reply needs "context" as the second member msgType = replyMessage } } else if i == 2 { if wasClientIndex && field.Name == contextField { // request needs "client_index" as the second member // and "context" as the third member msgType = requestMessage } } // skip internal fields switch strings.ToLower(field.Name) { case crcField, msgIdField: continue case clientIndexField, contextField: if n == 0 { continue } } n++ if n == 1 { fmt.Fprintln(w) } generateField(ctx, w, msg.Fields, i) } // generate end of the struct fmt.Fprintln(w, "}") // generate message methods generateMessageResetMethod(w, name) generateMessageNameGetter(w, name, msg.Name) generateCrcGetter(w, name, msg.CRC) generateMessageTypeGetter(w, name, msgType) fmt.Fprintln(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" && field.Length == 0 { fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName) } // check if it is array if field.Length > 0 || field.SizeFrom != "" { if dataType == "uint8" { dataType = "byte" } 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 && field.SpecifiedLen { // fixed size array fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType) } else { for _, f := range fields { if f.SizeFrom == field.Name { // variable sized array sizeOfName := camelCaseName(f.Name) fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName) } } } if ctx.includeBinapiNames { fieldTags["binapi"] = field.Name } if field.Meta.Limit > 0 { fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit) } if len(fieldTags) > 0 { fmt.Fprintf(w, "\t`") var keys []string for k := range fieldTags { keys = append(keys, k) } sort.Strings(keys) var n int for _, tt := range keys { t, ok := fieldTags[tt] if !ok { continue } if n > 0 { fmt.Fprintf(w, " ") } n++ fmt.Fprintf(w, `%s:"%s"`, tt, t) } fmt.Fprintf(w, "`") } fmt.Fprintln(w) } func generateMessageResetMethod(w io.Writer, structName string) { fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName) } func generateMessageNameGetter(w io.Writer, structName, msgName string) { fmt.Fprintf(w, "func (*%s) GetMessageName() string { return %q }\n", structName, msgName) } func generateTypeNameGetter(w io.Writer, structName, msgName string) { fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName) } func generateCrcGetter(w io.Writer, structName, crc string) { crc = strings.TrimPrefix(crc, "0x") fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc) } func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) { fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {") if msgType == requestMessage { fmt.Fprintf(w, "\treturn api.RequestMessage") } else if msgType == replyMessage { fmt.Fprintf(w, "\treturn api.ReplyMessage") } else if msgType == eventMessage { fmt.Fprintf(w, "\treturn api.EventMessage") } else { 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) }