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
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
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)
}
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]
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)
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, "}")
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 {
}
// 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) {
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)
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
}
// 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]
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)
}