X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=cmd%2Fbinapi-generator%2Fgenerate.go;h=715836dceb7fd3adeb2c33244d9d6dd6e49d8634;hb=d372b4efe26650dbc83908ca0bbee38d90aed3e9;hp=73bcd2a026624f3181be45200058042316b40a3e;hpb=6b350c65fe0ec845cecf58bfb41ffc63dc9c04f7;p=govpp.git diff --git a/cmd/binapi-generator/generate.go b/cmd/binapi-generator/generate.go index 73bcd2a..715836d 100644 --- a/cmd/binapi-generator/generate.go +++ b/cmd/binapi-generator/generate.go @@ -15,19 +15,36 @@ package main import ( - "bufio" "bytes" "fmt" "io" + "os/exec" + "path" "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 ( - 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 + inputFileExt = ".api.json" // file extension of the VPP 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 @@ -35,9 +52,14 @@ 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 - inputBuff *bytes.Buffer // contents of the input file currently being read - inputLine int // currently processed line in the input file + importPrefix string // defines import path prefix for importing types + + 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 @@ -45,8 +67,8 @@ type context struct { packageData *Package // parsed package data } -// getContext returns context details of the code generation task -func getContext(inputFile, outputDir string) (*context, error) { +// newContext returns context details of the code generation task +func newContext(inputFile, outputDir string) (*context, error) { if !strings.HasSuffix(inputFile, inputFileExt) { return nil, fmt.Errorf("invalid input file name: %q", inputFile) } @@ -77,186 +99,273 @@ 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("----------------------------") logf("generating package %q", ctx.packageName) + logf("----------------------------") + + fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") + fmt.Fprintf(w, "// source: %s\n", ctx.inputFile) + fmt.Fprintln(w) - // generate file header generateHeader(ctx, w) generateImports(ctx, w) - if *includeAPIVer { - const APIVerConstName = "VlAPIVersion" - fmt.Fprintf(w, "// %s represents version of the API.\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) + + 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 { - fmt.Fprintf(w, "/* Enums */\n\n") - - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 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 { - fmt.Fprintf(w, "/* Types */\n\n") - - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 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 { - fmt.Fprintf(w, "/* Unions */\n\n") - - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 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 { - fmt.Fprintf(w, "/* Messages */\n\n") - - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 0 for _, msg := range ctx.packageData.Messages { generateMessage(ctx, w, &msg) } - } - // generate services - if len(ctx.packageData.Services) > 0 { - fmt.Fprintf(w, "/* Services */\n\n") + // 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) - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 0 - fmt.Fprintf(w, "type %s interface {\n", "Services") - for _, svc := range ctx.packageData.Services { - generateService(ctx, w, &svc) + // 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, "}") } - // TODO: generate implementation for Services interface - - // 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) + if ctx.includeServices { + // generate services + if len(ctx.packageData.Services) > 0 { + generateServices(ctx, w, ctx.packageData.Services) + } } - fmt.Fprintln(w, "}") - // flush the data: - if err := w.Flush(); err != nil { - return fmt.Errorf("flushing data to %s failed: %v", ctx.outputFile, err) - } + 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 VPP binary API of the '%s' VPP module.\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 is generated from this file:") - fmt.Fprintf(w, "\t%s\n", filepath.Base(ctx.inputFile)) - fmt.Fprintln(w) - fmt.Fprintln(w, "It contains these VPP binary API 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 { - obj += "s" + if strings.HasSuffix(obj, "s") { + + obj += "es" + } else { + obj += "s" + } } - fmt.Fprintf(w, "\t%d %s\n", num, obj) + fmt.Fprintf(w, "\t%3d %s\n", num, obj) } } - printObjNum("message", len(ctx.packageData.Messages)) - printObjNum("type", len(ctx.packageData.Types)) 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) + } -// 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") + fmt.Fprintln(w, "import (") + fmt.Fprintln(w, ` "bytes"`) + fmt.Fprintln(w, ` "context"`) + fmt.Fprintln(w, ` "io"`) + fmt.Fprintln(w, ` "strconv"`) + fmt.Fprintln(w) + fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api") + fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc") + if len(ctx.packageData.Imports) > 0 { + fmt.Fprintln(w) + for _, imp := range getImports(ctx) { + importPath := path.Join(ctx.importPrefix, imp) + if importPath == "" { + importPath = getImportPath(filepath.Dir(ctx.outputFile), imp) + } + fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath)) + } + } + fmt.Fprintln(w, ")") + fmt.Fprintln(w) +} + +func getImportPath(outputDir string, pkg string) string { + absPath, _ := filepath.Abs(filepath.Join(outputDir, "..", pkg)) + cmd := exec.Command("go", "list", absPath) + var errbuf, outbuf bytes.Buffer + cmd.Stdout = &outbuf + cmd.Stderr = &errbuf + if err := cmd.Run(); err != nil { + fmt.Printf("ERR: %v\n", errbuf.String()) + panic(err) + } + return outbuf.String() +} + +func getImports(ctx *context) (imports []string) { + impmap := map[string]struct{}{} + for _, imp := range ctx.packageData.Imports { + if _, ok := impmap[imp.Package]; !ok { + imports = append(imports, imp.Package) + impmap[imp.Package] = struct{}{} + } + } + sort.Strings(imports) + return imports +} + +func generateFooter(ctx *context, w io.Writer) { + fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file") + fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.") + fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the") + fmt.Fprintln(w, "// GoVPP api package needs to be updated.") + fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion) fmt.Fprintln(w) fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n") fmt.Fprintf(w, "var _ = api.RegisterMessage\n") - fmt.Fprintf(w, "var _ = 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) { - fmt.Fprintf(w, "// %s represents the VPP binary API %s '%s'.\n", goName, objKind, vppName) + 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 := ctx.inputBuff.ReadString('\n') + line, err := inputBuff.ReadString('\n') if err != nil { break } - ctx.inputLine++ + 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.Fprintf(w, "// Generated from '%s', line %d:\n", filepath.Base(ctx.inputFile), ctx.inputLine) fmt.Fprintln(w, "//") } - } else { - if strings.IndexFunc(line, isNotSpace) < indent { - break // end of the object definition in JSON - } + } 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.Fprint(w, "//", line) + fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent)) } fmt.Fprintln(w, "//") } -// 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] @@ -270,54 +379,66 @@ 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) } -// generateType writes generated code for the type into w -func generateType(ctx *context, w io.Writer, typ *Type) { - name := camelCaseName(typ.Name) +func generateImportedAlias(ctx *context, w io.Writer, tName string, imp *Import) { + name := camelCaseName(tName) - logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields)) + fmt.Fprintf(w, "type %s = %s.%s\n", name, imp.Package, name) - // generate struct comment - generateComment(ctx, w, name, typ.Name, "type") + fmt.Fprintln(w) +} - // generate struct definition - fmt.Fprintf(w, "type %s struct {\n", name) +func generateAlias(ctx *context, w io.Writer, alias *Alias) { + name := camelCaseName(alias.Name) - // generate struct fields - for i, field := range typ.Fields { - // skip internal fields - switch strings.ToLower(field.Name) { - case "crc", "_vl_msg_id": - continue - } + logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length) - generateField(ctx, w, typ.Fields, i) - } + // generate struct comment + generateComment(ctx, w, name, alias.Name, "alias") - // generate end of the struct - fmt.Fprintln(w, "}") + // generate struct definition + fmt.Fprintf(w, "type %s ", name) - // generate name getter - generateTypeNameGetter(w, name, typ.Name) + if alias.Length > 0 { + fmt.Fprintf(w, "[%d]", alias.Length) + } - // generate CRC getter - generateCrcGetter(w, name, typ.CRC) + dataType := convertToGoType(ctx, alias.Type) + fmt.Fprintf(w, "%s\n", dataType) 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) @@ -333,8 +454,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, "}") @@ -343,7 +463,9 @@ func generateUnion(ctx *context, w io.Writer, union *Union) { generateTypeNameGetter(w, name, union.Name) // generate CRC getter - generateCrcGetter(w, name, union.CRC) + if union.CRC != "" { + generateCrcGetter(w, name, union.CRC) + } // generate getters for fields for _, field := range union.Fields { @@ -359,9 +481,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) { @@ -382,26 +504,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) @@ -420,26 +581,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 } @@ -455,20 +618,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) - // generate message factory - generateMessageFactory(w, name) + 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] @@ -476,90 +634,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) } } } - 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) + if ctx.includeBinapiNames { + fieldTags["binapi"] = field.Name + } + if field.Meta.Limit > 0 { + fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit) + } - // method name is same as parameter type name by default - method := svc.MethodName() - params := fmt.Sprintf("*%s", reqTyp) - returns := "error" - if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" { - returns = fmt.Sprintf("(*%s, error)", replyTyp) + 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.Fprintf(w, "\t%s(%s) %s\n", method, params, returns) + 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) } -// generateMessageFactory generates message factory for the generated message into the provider writer -func generateMessageFactory(w io.Writer, structName string) { - fmt.Fprintln(w, "func New"+structName+"() api.Message {") - fmt.Fprintln(w, "\treturn &"+structName+"{}") +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) }