X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=cmd%2Fbinapi-generator%2Fgenerate.go;h=fb6cee5c903d26b14c85e839cf35938414f41515;hb=2df59463fcbb1a5aec2173712b32fb9740157a9d;hp=22b4af65279762c15fed88c6151819822818291f;hpb=5f1917fd11e8562ef4094b7ce3a89af66ace5792;p=govpp.git diff --git a/cmd/binapi-generator/generate.go b/cmd/binapi-generator/generate.go index 22b4af6..fb6cee5 100644 --- a/cmd/binapi-generator/generate.go +++ b/cmd/binapi-generator/generate.go @@ -15,19 +15,37 @@ package main import ( - "bufio" "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 - inputFileExt = ".api.json" // file extension of the VPP binary API files - outputFileExt = ".ba.go" // file extension of the Go generated files + + 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 @@ -37,14 +55,19 @@ type context struct { 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 } -// 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) } @@ -75,104 +98,124 @@ 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 *bufio.Writer) error { +func generatePackage(ctx *context, w io.Writer) error { logf("generating package %q", ctx.packageName) - // generate file header + fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") + fmt.Fprintf(w, "// source: %s\n", ctx.inputFile) + fmt.Fprintln(w) + generateHeader(ctx, w) - generateImports(ctx, w) - if *includeAPIVer { - const APIVerConstName = "VlAPIVersion" - fmt.Fprintf(w, "// %s represents version of the binary API module.\n", APIVerConstName) - fmt.Fprintf(w, "const %s = %v\n", APIVerConstName, ctx.packageData.APIVersion) - fmt.Fprintln(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) - // generate services - if len(ctx.packageData.Services) > 0 { - generateServices(ctx, w, ctx.packageData.Services) + 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) } - - // TODO: generate implementation for Services interface + fmt.Fprintln(w, ")") + fmt.Fprintln(w) // 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) } - } - // generate message registrations - fmt.Fprintln(w) - 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) + // 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, "}") } - fmt.Fprintln(w, "}") - // flush the data: - if err := w.Flush(); err != nil { - return fmt.Errorf("flushing data to %s failed: %v", ctx.outputFile, err) + if ctx.includeServices { + // generate services + if len(ctx.packageData.Services) > 0 { + generateServices(ctx, w, ctx.packageData.Services) + } } + 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.Fprintln(w, " It contains following objects:") - 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") { + obj += "es" } else { obj += "s" @@ -181,38 +224,85 @@ func generateHeader(ctx *context, w io.Writer) { fmt.Fprintf(w, "\t%3d %s\n", num, obj) } } - printObjNum("message", len(ctx.packageData.Messages)) - printObjNum("type", len(ctx.packageData.Types)) - printObjNum("alias", len(ctx.packageData.Aliases)) 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.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 } -// generateImports writes generated package imports into w -func generateImports(ctx *context, w io.Writer) { - fmt.Fprintf(w, "import \"%s\"\n", govppApiImportPath) - fmt.Fprintf(w, "import \"%s\"\n", "github.com/lunixbochs/struc") - fmt.Fprintf(w, "import \"%s\"\n", "bytes") +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 _ = struc.Pack\n") fmt.Fprintf(w, "var _ = bytes.NewBuffer\n") - fmt.Fprintln(w) + 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:\n", 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 { + return } var isNotSpace = func(r rune) bool { @@ -265,41 +355,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 *bufio.Writer, services []Service) { - // generate services comment - generateComment(ctx, w, "Services", "services", "service") - - // generate interface - fmt.Fprintf(w, "type %s interface {\n", "Services") - for _, svc := range ctx.packageData.Services { - generateService(ctx, w, &svc) - } - fmt.Fprintln(w, "}") - - fmt.Fprintln(w) -} - -// generateService writes generated code for the service into w -func generateService(ctx *context, w io.Writer, svc *Service) { - reqTyp := camelCaseName(svc.RequestType) - - // method name is same as parameter type name by default - method := svc.MethodName() - params := fmt.Sprintf("*%s", reqTyp) - returns := "error" - if replyType := camelCaseName(svc.ReplyType); replyType != "" { - repTyp := fmt.Sprintf("*%s", replyType) - if svc.Stream { - repTyp = fmt.Sprintf("[]%s", repTyp) - } - returns = fmt.Sprintf("(%s, error)", repTyp) - } - - fmt.Fprintf(w, "\t%s(%s) %s\n", 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] @@ -313,19 +368,45 @@ func generateEnum(ctx *context, w io.Writer, enum *Enum) { fmt.Fprintf(w, "type %s %s\n", name, typ) fmt.Fprintln(w) - fmt.Fprintln(w, "const (") - // 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) } -// generateAlias writes generated code for the alias into w func generateAlias(ctx *context, w io.Writer, alias *Alias) { name := camelCaseName(alias.Name) @@ -347,42 +428,6 @@ func generateAlias(ctx *context, w io.Writer, alias *Alias) { fmt.Fprintln(w) } -// generateType writes generated code for the type into w -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 "crc", "_vl_msg_id": - 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 - generateCrcGetter(w, name, typ.CRC) - - 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) @@ -398,8 +443,7 @@ func generateUnion(ctx *context, w io.Writer, union *Union) { maxSize := getUnionSize(ctx, union) // generate data field - fieldName := "Union_data" - fmt.Fprintf(w, "\t%s [%d]byte\n", fieldName, maxSize) + fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize) // generate end of the struct fmt.Fprintln(w, "}") @@ -408,7 +452,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 { @@ -424,9 +470,9 @@ func generateUnion(ctx *context, w io.Writer, union *Union) { } // generateUnionMethods generates methods that implement struc.Custom -// interface to allow having Union_data field unexported +// 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) { +/*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) { @@ -447,26 +493,65 @@ 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.Union_data[:], b.Bytes()) + copy(u.%[4]s[:], b.Bytes()) } func (u *%[1]s) Get%[2]s() (a %[3]s) { - var b = bytes.NewReader(u.Union_data[:]) + var b = bytes.NewReader(u.%[4]s[:]) struc.Unpack(b, &a) return } -`, structName, getterField, getterStruct) +`, 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) } -// generateMessage writes generated code for the message into w func generateMessage(ctx *context, w io.Writer, msg *Message) { name := camelCaseName(msg.Name) @@ -485,26 +570,28 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) { n := 0 for i, field := range msg.Fields { if i == 1 { - if field.Name == "client_index" { - // "client_index" as the second member, this might be an event message or a request + 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 == "context" { + } else if field.Name == contextField { // reply needs "context" as the second member msgType = replyMessage } } else if i == 2 { - if wasClientIndex && field.Name == "context" { - // request needs "client_index" as the second member and "context" as the third member + 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 "crc", "_vl_msg_id": + case crcField, msgIdField: continue - case "client_index", "context": + case clientIndexField, contextField: if n == 0 { continue } @@ -520,17 +607,15 @@ 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] @@ -538,68 +623,215 @@ func generateField(ctx *context, w io.Writer, fields []Field, i int) { fieldName = camelCaseName(fieldName) dataType := convertToGoType(ctx, field.Type) - fieldType := dataType - if field.IsArray() { + + // 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" } - fieldType = "[]" + dataType + if dataType == "string" && field.SpecifiedLen { + fieldType = "string" + dataType = "byte" + } else { + fieldType = "[]" + dataType + } } fmt.Fprintf(w, "\t%s %s", fieldName, fieldType) - if field.Length > 0 { + fieldTags := map[string]string{} + + if field.Length > 0 && field.SpecifiedLen { // fixed size array - fmt.Fprintf(w, "\t`struc:\"[%d]%s\"`", field.Length, dataType) + 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) - fmt.Fprintf(w, "\t`struc:\"sizeof=%s\"`", sizeOfName) + 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) } -// 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) }