1 // Copyright (c) 2017 Cisco and/or its affiliates.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
27 // generatedCodeVersion indicates a version of the generated code.
28 // It is incremented whenever an incompatibility between the generated code and
29 // GoVPP api package is introduced; the generated code references
30 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
31 const generatedCodeVersion = 1
34 inputFileExt = ".api.json" // file extension of the VPP API files
35 outputFileExt = ".ba.go" // file extension of the Go generated files
37 govppApiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API package
39 constModuleName = "ModuleName" // module name constant
40 constAPIVersion = "APIVersion" // API version constant
41 constVersionCrc = "VersionCrc" // version CRC constant
43 unionDataField = "XXX_UnionData" // name for the union data field
46 // context is a structure storing data for code generation
48 inputFile string // input file with VPP API in JSON
49 outputFile string // output file with generated Go package
51 inputData []byte // contents of the input file
53 includeAPIVersion bool // include constant with API version string
54 includeComments bool // include parts of original source in comments
55 includeBinapiNames bool // include binary API names as struct tag
56 includeServices bool // include service interface with client implementation
58 moduleName string // name of the source VPP module
59 packageName string // name of the Go package being generated
61 packageData *Package // parsed package data
64 // getContext returns context details of the code generation task
65 func getContext(inputFile, outputDir string) (*context, error) {
66 if !strings.HasSuffix(inputFile, inputFileExt) {
67 return nil, fmt.Errorf("invalid input file name: %q", inputFile)
75 inputFileName := filepath.Base(inputFile)
76 ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
78 // alter package names for modules that are reserved keywords in Go
79 switch ctx.moduleName {
81 ctx.packageName = "interfaces"
83 ctx.packageName = "maps"
85 ctx.packageName = ctx.moduleName
89 packageDir := filepath.Join(outputDir, ctx.packageName)
90 outputFileName := ctx.packageName + outputFileExt
91 ctx.outputFile = filepath.Join(packageDir, outputFileName)
96 // generatePackage generates code for the parsed package data and writes it into w
97 func generatePackage(ctx *context, w io.Writer) error {
98 logf("generating package %q", ctx.packageName)
100 // generate file header
101 generateHeader(ctx, w)
102 generateImports(ctx, w)
104 // generate module desc
105 fmt.Fprintln(w, "const (")
106 fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
107 fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName)
109 if ctx.includeAPIVersion {
110 if ctx.packageData.Version != "" {
111 fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
112 fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
114 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
115 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
121 if len(ctx.packageData.Enums) > 0 {
122 fmt.Fprintf(w, "/* Enums */\n\n")
124 for _, enum := range ctx.packageData.Enums {
125 generateEnum(ctx, w, &enum)
130 if len(ctx.packageData.Aliases) > 0 {
131 fmt.Fprintf(w, "/* Aliases */\n\n")
133 for _, alias := range ctx.packageData.Aliases {
134 generateAlias(ctx, w, &alias)
139 if len(ctx.packageData.Types) > 0 {
140 fmt.Fprintf(w, "/* Types */\n\n")
142 for _, typ := range ctx.packageData.Types {
143 generateType(ctx, w, &typ)
148 if len(ctx.packageData.Unions) > 0 {
149 fmt.Fprintf(w, "/* Unions */\n\n")
151 for _, union := range ctx.packageData.Unions {
152 generateUnion(ctx, w, &union)
157 if len(ctx.packageData.Messages) > 0 {
158 fmt.Fprintf(w, "/* Messages */\n\n")
160 for _, msg := range ctx.packageData.Messages {
161 generateMessage(ctx, w, &msg)
164 // generate message registrations
165 fmt.Fprintln(w, "func init() {")
166 for _, msg := range ctx.packageData.Messages {
167 name := camelCaseName(msg.Name)
168 fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
173 // generate list of messages
174 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
175 fmt.Fprintln(w, "func AllMessages() []api.Message {")
176 fmt.Fprintln(w, "\treturn []api.Message{")
177 for _, msg := range ctx.packageData.Messages {
178 name := camelCaseName(msg.Name)
179 fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
185 if ctx.includeServices {
187 if len(ctx.packageData.Services) > 0 {
188 generateServices(ctx, w, ctx.packageData.Services)
195 // generateHeader writes generated package header into w
196 func generateHeader(ctx *context, w io.Writer) {
197 fmt.Fprintln(w, "// Code generated by GoVPP binapi-generator. DO NOT EDIT.")
198 fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
201 fmt.Fprintln(w, "/*")
202 fmt.Fprintf(w, "Package %s is a generated from VPP binary API module '%s'.\n", ctx.packageName, ctx.moduleName)
204 fmt.Fprintf(w, " The %s module consists of:\n", ctx.moduleName)
205 var printObjNum = func(obj string, num int) {
208 if strings.HasSuffix(obj, "s") {
215 fmt.Fprintf(w, "\t%3d %s\n", num, obj)
219 printObjNum("enum", len(ctx.packageData.Enums))
220 printObjNum("alias", len(ctx.packageData.Aliases))
221 printObjNum("type", len(ctx.packageData.Types))
222 printObjNum("union", len(ctx.packageData.Unions))
223 printObjNum("message", len(ctx.packageData.Messages))
224 printObjNum("service", len(ctx.packageData.Services))
225 fmt.Fprintln(w, "*/")
227 fmt.Fprintf(w, "package %s\n", ctx.packageName)
231 // generateImports writes generated package imports into w
232 func generateImports(ctx *context, w io.Writer) {
233 fmt.Fprintf(w, "import api \"%s\"\n", govppApiImportPath)
234 fmt.Fprintf(w, "import bytes \"%s\"\n", "bytes")
235 fmt.Fprintf(w, "import context \"%s\"\n", "context")
236 fmt.Fprintf(w, "import strconv \"%s\"\n", "strconv")
237 fmt.Fprintf(w, "import struc \"%s\"\n", "github.com/lunixbochs/struc")
240 fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
241 fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
242 fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
243 fmt.Fprintf(w, "var _ = context.Background\n")
244 fmt.Fprintf(w, "var _ = strconv.Itoa\n")
245 fmt.Fprintf(w, "var _ = struc.Pack\n")
248 fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
249 fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
250 fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
251 fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
252 fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
256 // generateComment writes generated comment for the object into w
257 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
258 if objKind == "service" {
259 fmt.Fprintf(w, "// %s represents VPP binary API services in %s module.\n", goName, ctx.moduleName)
261 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s':\n", goName, objKind, vppName)
264 if !ctx.includeComments {
268 var isNotSpace = func(r rune) bool {
269 return !unicode.IsSpace(r)
272 // print out the source of the generated object
275 objTitle := fmt.Sprintf(`"%s",`, vppName)
277 case "alias", "service":
278 objTitle = fmt.Sprintf(`"%s": {`, vppName)
282 inputBuff := bytes.NewBuffer(ctx.inputData)
285 var trimIndent string
288 line, err := inputBuff.ReadString('\n')
294 noSpaceAt := strings.IndexFunc(line, isNotSpace)
296 indent = strings.Index(line, objTitle)
300 trimIndent = line[:indent]
301 // If no other non-whitespace character then we are at the message header.
302 if trimmed := strings.TrimSpace(line); trimmed == objTitle {
304 fmt.Fprintln(w, "//")
306 } else if noSpaceAt < indent {
307 break // end of the definition in JSON for array types
308 } else if objFound && mapType && noSpaceAt <= indent {
309 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
310 break // end of the definition in JSON for map types (aliases, services)
312 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
315 fmt.Fprintln(w, "//")
318 // generateServices writes generated code for the Services interface into w
319 func generateServices(ctx *context, w io.Writer, services []Service) {
320 const apiName = "Service"
321 const implName = "service"
323 // generate services comment
324 generateComment(ctx, w, apiName, "services", "service")
326 // generate interface
327 fmt.Fprintf(w, "type %s interface {\n", apiName)
328 for _, svc := range services {
329 generateServiceMethod(ctx, w, &svc)
335 // generate client implementation
336 fmt.Fprintf(w, "type %s struct {\n", implName)
337 fmt.Fprintf(w, "\tch api.Channel\n")
341 fmt.Fprintf(w, "func New%[1]s(ch api.Channel) %[1]s {\n", apiName)
342 fmt.Fprintf(w, "\treturn &%s{ch}\n", implName)
346 for _, svc := range services {
347 fmt.Fprintf(w, "func (c *%s) ", implName)
348 generateServiceMethod(ctx, w, &svc)
349 fmt.Fprintln(w, " {")
351 // TODO: stream responses
352 //fmt.Fprintf(w, "\tstream := make(chan *%s)\n", camelCaseName(svc.ReplyType))
353 replyTyp := camelCaseName(svc.ReplyType)
354 fmt.Fprintf(w, "\tvar dump []*%s\n", replyTyp)
355 fmt.Fprintf(w, "\treq := c.ch.SendMultiRequest(in)\n")
356 fmt.Fprintf(w, "\tfor {\n")
357 fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
358 fmt.Fprintf(w, "\tstop, err := req.ReceiveReply(m)\n")
359 fmt.Fprintf(w, "\tif stop { break }\n")
360 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
361 fmt.Fprintf(w, "\tdump = append(dump, m)\n")
363 fmt.Fprintf(w, "\treturn dump, nil\n")
364 } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
365 fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
366 fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
367 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
368 fmt.Fprintf(w, "\treturn out, nil\n")
370 fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
371 fmt.Fprintf(w, "\treturn nil\n")
380 // generateServiceMethod writes generated code for the service into w
381 func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
382 reqTyp := camelCaseName(svc.RequestType)
384 // method name is same as parameter type name by default
387 // use Dump as prefix instead of suffix for stream services
388 if m := strings.TrimSuffix(method, "Dump"); method != m {
393 params := fmt.Sprintf("in *%s", reqTyp)
395 if replyType := camelCaseName(svc.ReplyType); replyType != "" {
396 replyTyp := fmt.Sprintf("*%s", replyType)
398 // TODO: stream responses
399 //replyTyp = fmt.Sprintf("<-chan %s", replyTyp)
400 replyTyp = fmt.Sprintf("[]%s", replyTyp)
402 returns = fmt.Sprintf("(%s, error)", replyTyp)
405 fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
408 // generateEnum writes generated code for the enum into w
409 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
410 name := camelCaseName(enum.Name)
411 typ := binapiTypes[enum.Type]
413 logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
415 // generate enum comment
416 generateComment(ctx, w, name, enum.Name, "enum")
418 // generate enum definition
419 fmt.Fprintf(w, "type %s %s\n", name, typ)
422 // generate enum entries
423 fmt.Fprintln(w, "const (")
424 for _, entry := range enum.Entries {
425 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
430 // generate enum conversion maps
431 fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
432 for _, entry := range enum.Entries {
433 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
438 fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
439 for _, entry := range enum.Entries {
440 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
445 fmt.Fprintf(w, "func (x %s) String() string {\n", name)
446 fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
447 fmt.Fprintf(w, "\tif ok { return s }\n")
448 fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
453 // generateAlias writes generated code for the alias into w
454 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
455 name := camelCaseName(alias.Name)
457 logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
459 // generate struct comment
460 generateComment(ctx, w, name, alias.Name, "alias")
462 // generate struct definition
463 fmt.Fprintf(w, "type %s ", name)
465 if alias.Length > 0 {
466 fmt.Fprintf(w, "[%d]", alias.Length)
469 dataType := convertToGoType(ctx, alias.Type)
470 fmt.Fprintf(w, "%s\n", dataType)
475 // generateUnion writes generated code for the union into w
476 func generateUnion(ctx *context, w io.Writer, union *Union) {
477 name := camelCaseName(union.Name)
479 logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
481 // generate struct comment
482 generateComment(ctx, w, name, union.Name, "union")
484 // generate struct definition
485 fmt.Fprintln(w, "type", name, "struct {")
487 // maximum size for union
488 maxSize := getUnionSize(ctx, union)
490 // generate data field
491 fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
493 // generate end of the struct
496 // generate name getter
497 generateTypeNameGetter(w, name, union.Name)
499 // generate CRC getter
501 generateCrcGetter(w, name, union.CRC)
504 // generate getters for fields
505 for _, field := range union.Fields {
506 fieldName := camelCaseName(field.Name)
507 fieldType := convertToGoType(ctx, field.Type)
508 generateUnionGetterSetter(w, name, fieldName, fieldType)
511 // generate union methods
512 //generateUnionMethods(w, name)
517 // generateUnionMethods generates methods that implement struc.Custom
518 // interface to allow having XXX_uniondata field unexported
519 // TODO: do more testing when unions are actually used in some messages
520 /*func generateUnionMethods(w io.Writer, structName string) {
521 // generate struc.Custom implementation for union
523 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
524 var b = new(bytes.Buffer)
525 if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
531 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
532 return struc.UnpackWithOptions(r, u.union_data[:], opt)
534 func (u *%[1]s) Size(opt *struc.Options) int {
535 return len(u.union_data)
537 func (u *%[1]s) String() string {
538 return string(u.union_data[:])
543 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
545 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
549 func (u *%[1]s) Set%[2]s(a %[3]s) {
550 var b = new(bytes.Buffer)
551 if err := struc.Pack(b, &a); err != nil {
554 copy(u.%[4]s[:], b.Bytes())
556 func (u *%[1]s) Get%[2]s() (a %[3]s) {
557 var b = bytes.NewReader(u.%[4]s[:])
561 `, structName, getterField, getterStruct, unionDataField)
564 // generateType writes generated code for the type into w
565 func generateType(ctx *context, w io.Writer, typ *Type) {
566 name := camelCaseName(typ.Name)
568 logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
570 // generate struct comment
571 generateComment(ctx, w, name, typ.Name, "type")
573 // generate struct definition
574 fmt.Fprintf(w, "type %s struct {\n", name)
576 // generate struct fields
577 for i, field := range typ.Fields {
578 // skip internal fields
579 switch strings.ToLower(field.Name) {
580 case crcField, msgIdField:
584 generateField(ctx, w, typ.Fields, i)
587 // generate end of the struct
590 // generate name getter
591 generateTypeNameGetter(w, name, typ.Name)
593 // generate CRC getter
595 generateCrcGetter(w, name, typ.CRC)
601 // generateMessage writes generated code for the message into w
602 func generateMessage(ctx *context, w io.Writer, msg *Message) {
603 name := camelCaseName(msg.Name)
605 logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
607 // generate struct comment
608 generateComment(ctx, w, name, msg.Name, "message")
610 // generate struct definition
611 fmt.Fprintf(w, "type %s struct {", name)
613 msgType := otherMessage
614 wasClientIndex := false
616 // generate struct fields
618 for i, field := range msg.Fields {
620 if field.Name == clientIndexField {
621 // "client_index" as the second member,
622 // this might be an event message or a request
623 msgType = eventMessage
624 wasClientIndex = true
625 } else if field.Name == contextField {
626 // reply needs "context" as the second member
627 msgType = replyMessage
630 if wasClientIndex && field.Name == contextField {
631 // request needs "client_index" as the second member
632 // and "context" as the third member
633 msgType = requestMessage
637 // skip internal fields
638 switch strings.ToLower(field.Name) {
639 case crcField, msgIdField:
641 case clientIndexField, contextField:
651 generateField(ctx, w, msg.Fields, i)
654 // generate end of the struct
657 // generate name getter
658 generateMessageNameGetter(w, name, msg.Name)
660 // generate CRC getter
661 generateCrcGetter(w, name, msg.CRC)
663 // generate message type getter method
664 generateMessageTypeGetter(w, name, msgType)
669 // generateField writes generated code for the field into w
670 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
673 fieldName := strings.TrimPrefix(field.Name, "_")
674 fieldName = camelCaseName(fieldName)
676 // generate length field for strings
677 if field.Type == "string" {
678 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
681 dataType := convertToGoType(ctx, field.Type)
682 fieldType := dataType
684 // check if it is array
685 if field.Length > 0 || field.SizeFrom != "" {
686 if dataType == "uint8" {
689 fieldType = "[]" + dataType
691 fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
693 fieldTags := map[string]string{}
695 if field.Length > 0 {
697 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
699 for _, f := range fields {
700 if f.SizeFrom == field.Name {
701 // variable sized array
702 sizeOfName := camelCaseName(f.Name)
703 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
708 if ctx.includeBinapiNames {
709 fieldTags["binapi"] = field.Name
711 if field.Meta.Limit > 0 {
712 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
715 if len(fieldTags) > 0 {
716 fmt.Fprintf(w, "\t`")
718 for k := range fieldTags {
719 keys = append(keys, k)
723 for _, tt := range keys {
724 t, ok := fieldTags[tt]
732 fmt.Fprintf(w, `%s:"%s"`, tt, t)
740 // generateMessageNameGetter generates getter for original VPP message name into the provider writer
741 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
742 fmt.Fprintf(w, `func (*%s) GetMessageName() string {
745 `, structName, msgName)
748 // generateTypeNameGetter generates getter for original VPP type name into the provider writer
749 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
750 fmt.Fprintf(w, `func (*%s) GetTypeName() string {
753 `, structName, msgName)
756 // generateCrcGetter generates getter for CRC checksum of the message definition into the provider writer
757 func generateCrcGetter(w io.Writer, structName, crc string) {
758 crc = strings.TrimPrefix(crc, "0x")
759 fmt.Fprintf(w, `func (*%s) GetCrcString() string {
765 // generateMessageTypeGetter generates message factory for the generated message into the provider writer
766 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
767 fmt.Fprintln(w, "func (*"+structName+") GetMessageType() api.MessageType {")
768 if msgType == requestMessage {
769 fmt.Fprintln(w, "\treturn api.RequestMessage")
770 } else if msgType == replyMessage {
771 fmt.Fprintln(w, "\treturn api.ReplyMessage")
772 } else if msgType == eventMessage {
773 fmt.Fprintln(w, "\treturn api.EventMessage")
775 fmt.Fprintln(w, "\treturn api.OtherMessage")