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
45 serviceApiName = "RPCService" // name for the RPC service interface
46 serviceImplName = "serviceClient" // name for the RPC service implementation
47 serviceClientName = "ServiceClient" // name for the RPC service client
50 // context is a structure storing data for code generation
52 inputFile string // input file with VPP API in JSON
53 outputFile string // output file with generated Go package
55 inputData []byte // contents of the input file
57 includeAPIVersion bool // include constant with API version string
58 includeComments bool // include parts of original source in comments
59 includeBinapiNames bool // include binary API names as struct tag
60 includeServices bool // include service interface with client implementation
62 moduleName string // name of the source VPP module
63 packageName string // name of the Go package being generated
65 packageData *Package // parsed package data
68 // newContext returns context details of the code generation task
69 func newContext(inputFile, outputDir string) (*context, error) {
70 if !strings.HasSuffix(inputFile, inputFileExt) {
71 return nil, fmt.Errorf("invalid input file name: %q", inputFile)
79 inputFileName := filepath.Base(inputFile)
80 ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
82 // alter package names for modules that are reserved keywords in Go
83 switch ctx.moduleName {
85 ctx.packageName = "interfaces"
87 ctx.packageName = "maps"
89 ctx.packageName = ctx.moduleName
93 packageDir := filepath.Join(outputDir, ctx.packageName)
94 outputFileName := ctx.packageName + outputFileExt
95 ctx.outputFile = filepath.Join(packageDir, outputFileName)
100 func generatePackage(ctx *context, w io.Writer) error {
101 logf("generating package %q", ctx.packageName)
103 fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
104 fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
107 generateHeader(ctx, w)
109 // generate module desc
110 fmt.Fprintln(w, "const (")
111 fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
112 fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName)
114 if ctx.includeAPIVersion {
115 if ctx.packageData.Version != "" {
116 fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
117 fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
119 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
120 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
126 if len(ctx.packageData.Enums) > 0 {
127 for _, enum := range ctx.packageData.Enums {
128 generateEnum(ctx, w, &enum)
133 if len(ctx.packageData.Aliases) > 0 {
134 for _, alias := range ctx.packageData.Aliases {
135 generateAlias(ctx, w, &alias)
140 if len(ctx.packageData.Types) > 0 {
141 for _, typ := range ctx.packageData.Types {
142 generateType(ctx, w, &typ)
147 if len(ctx.packageData.Unions) > 0 {
148 for _, union := range ctx.packageData.Unions {
149 generateUnion(ctx, w, &union)
154 if len(ctx.packageData.Messages) > 0 {
155 for _, msg := range ctx.packageData.Messages {
156 generateMessage(ctx, w, &msg)
159 // generate message registrations
160 fmt.Fprintln(w, "func init() {")
161 for _, msg := range ctx.packageData.Messages {
162 name := camelCaseName(msg.Name)
163 fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
168 // generate list of messages
169 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
170 fmt.Fprintln(w, "func AllMessages() []api.Message {")
171 fmt.Fprintln(w, "\treturn []api.Message{")
172 for _, msg := range ctx.packageData.Messages {
173 name := camelCaseName(msg.Name)
174 fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
180 if ctx.includeServices {
182 if len(ctx.packageData.Services) > 0 {
183 generateServices(ctx, w, ctx.packageData.Services)
187 generateFooter(ctx, w)
192 func generateHeader(ctx *context, w io.Writer) {
193 fmt.Fprintln(w, "/*")
194 fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
196 fmt.Fprintln(w, "It consists of:")
197 printObjNum := func(obj string, num int) {
200 if strings.HasSuffix(obj, "s") {
207 fmt.Fprintf(w, "\t%3d %s\n", num, obj)
210 printObjNum("enum", len(ctx.packageData.Enums))
211 printObjNum("alias", len(ctx.packageData.Aliases))
212 printObjNum("type", len(ctx.packageData.Types))
213 printObjNum("union", len(ctx.packageData.Unions))
214 printObjNum("message", len(ctx.packageData.Messages))
215 printObjNum("service", len(ctx.packageData.Services))
216 fmt.Fprintln(w, "*/")
217 fmt.Fprintf(w, "package %s\n", ctx.packageName)
220 fmt.Fprintln(w, "import (")
221 fmt.Fprintf(w, "\tapi \"%s\"\n", govppApiImportPath)
222 fmt.Fprintf(w, "\tbytes \"%s\"\n", "bytes")
223 fmt.Fprintf(w, "\tcontext \"%s\"\n", "context")
224 fmt.Fprintf(w, "\tio \"%s\"\n", "io")
225 fmt.Fprintf(w, "\tstrconv \"%s\"\n", "strconv")
226 fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
231 func generateFooter(ctx *context, w io.Writer) {
232 fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
233 fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
234 fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
235 fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
236 fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
239 fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
240 fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
241 fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
242 fmt.Fprintf(w, "var _ = context.Background\n")
243 fmt.Fprintf(w, "var _ = io.Copy\n")
244 fmt.Fprintf(w, "var _ = strconv.Itoa\n")
245 fmt.Fprintf(w, "var _ = struc.Pack\n")
248 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
249 if objKind == "service" {
250 fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
252 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
255 if !ctx.includeComments {
259 var isNotSpace = func(r rune) bool {
260 return !unicode.IsSpace(r)
263 // print out the source of the generated object
266 objTitle := fmt.Sprintf(`"%s",`, vppName)
268 case "alias", "service":
269 objTitle = fmt.Sprintf(`"%s": {`, vppName)
273 inputBuff := bytes.NewBuffer(ctx.inputData)
276 var trimIndent string
279 line, err := inputBuff.ReadString('\n')
285 noSpaceAt := strings.IndexFunc(line, isNotSpace)
287 indent = strings.Index(line, objTitle)
291 trimIndent = line[:indent]
292 // If no other non-whitespace character then we are at the message header.
293 if trimmed := strings.TrimSpace(line); trimmed == objTitle {
295 fmt.Fprintln(w, "//")
297 } else if noSpaceAt < indent {
298 break // end of the definition in JSON for array types
299 } else if objFound && mapType && noSpaceAt <= indent {
300 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
301 break // end of the definition in JSON for map types (aliases, services)
303 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
306 fmt.Fprintln(w, "//")
309 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
310 name := camelCaseName(enum.Name)
311 typ := binapiTypes[enum.Type]
313 logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
315 // generate enum comment
316 generateComment(ctx, w, name, enum.Name, "enum")
318 // generate enum definition
319 fmt.Fprintf(w, "type %s %s\n", name, typ)
322 // generate enum entries
323 fmt.Fprintln(w, "const (")
324 for _, entry := range enum.Entries {
325 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
330 // generate enum conversion maps
331 fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
332 for _, entry := range enum.Entries {
333 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
338 fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
339 for _, entry := range enum.Entries {
340 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
345 fmt.Fprintf(w, "func (x %s) String() string {\n", name)
346 fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
347 fmt.Fprintf(w, "\tif ok { return s }\n")
348 fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
353 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
354 name := camelCaseName(alias.Name)
356 logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
358 // generate struct comment
359 generateComment(ctx, w, name, alias.Name, "alias")
361 // generate struct definition
362 fmt.Fprintf(w, "type %s ", name)
364 if alias.Length > 0 {
365 fmt.Fprintf(w, "[%d]", alias.Length)
368 dataType := convertToGoType(ctx, alias.Type)
369 fmt.Fprintf(w, "%s\n", dataType)
374 func generateUnion(ctx *context, w io.Writer, union *Union) {
375 name := camelCaseName(union.Name)
377 logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
379 // generate struct comment
380 generateComment(ctx, w, name, union.Name, "union")
382 // generate struct definition
383 fmt.Fprintln(w, "type", name, "struct {")
385 // maximum size for union
386 maxSize := getUnionSize(ctx, union)
388 // generate data field
389 fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
391 // generate end of the struct
394 // generate name getter
395 generateTypeNameGetter(w, name, union.Name)
397 // generate CRC getter
399 generateCrcGetter(w, name, union.CRC)
402 // generate getters for fields
403 for _, field := range union.Fields {
404 fieldName := camelCaseName(field.Name)
405 fieldType := convertToGoType(ctx, field.Type)
406 generateUnionGetterSetter(w, name, fieldName, fieldType)
409 // generate union methods
410 //generateUnionMethods(w, name)
415 // generateUnionMethods generates methods that implement struc.Custom
416 // interface to allow having XXX_uniondata field unexported
417 // TODO: do more testing when unions are actually used in some messages
418 /*func generateUnionMethods(w io.Writer, structName string) {
419 // generate struc.Custom implementation for union
421 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
422 var b = new(bytes.Buffer)
423 if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
429 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
430 return struc.UnpackWithOptions(r, u.union_data[:], opt)
432 func (u *%[1]s) Size(opt *struc.Options) int {
433 return len(u.union_data)
435 func (u *%[1]s) String() string {
436 return string(u.union_data[:])
441 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
443 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
447 func (u *%[1]s) Set%[2]s(a %[3]s) {
448 var b = new(bytes.Buffer)
449 if err := struc.Pack(b, &a); err != nil {
452 copy(u.%[4]s[:], b.Bytes())
454 func (u *%[1]s) Get%[2]s() (a %[3]s) {
455 var b = bytes.NewReader(u.%[4]s[:])
459 `, structName, getterField, getterStruct, unionDataField)
462 func generateType(ctx *context, w io.Writer, typ *Type) {
463 name := camelCaseName(typ.Name)
465 logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
467 // generate struct comment
468 generateComment(ctx, w, name, typ.Name, "type")
470 // generate struct definition
471 fmt.Fprintf(w, "type %s struct {\n", name)
473 // generate struct fields
474 for i, field := range typ.Fields {
475 // skip internal fields
476 switch strings.ToLower(field.Name) {
477 case crcField, msgIdField:
481 generateField(ctx, w, typ.Fields, i)
484 // generate end of the struct
487 // generate name getter
488 generateTypeNameGetter(w, name, typ.Name)
490 // generate CRC getter
492 generateCrcGetter(w, name, typ.CRC)
498 func generateMessage(ctx *context, w io.Writer, msg *Message) {
499 name := camelCaseName(msg.Name)
501 logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
503 // generate struct comment
504 generateComment(ctx, w, name, msg.Name, "message")
506 // generate struct definition
507 fmt.Fprintf(w, "type %s struct {", name)
509 msgType := otherMessage
510 wasClientIndex := false
512 // generate struct fields
514 for i, field := range msg.Fields {
516 if field.Name == clientIndexField {
517 // "client_index" as the second member,
518 // this might be an event message or a request
519 msgType = eventMessage
520 wasClientIndex = true
521 } else if field.Name == contextField {
522 // reply needs "context" as the second member
523 msgType = replyMessage
526 if wasClientIndex && field.Name == contextField {
527 // request needs "client_index" as the second member
528 // and "context" as the third member
529 msgType = requestMessage
533 // skip internal fields
534 switch strings.ToLower(field.Name) {
535 case crcField, msgIdField:
537 case clientIndexField, contextField:
547 generateField(ctx, w, msg.Fields, i)
550 // generate end of the struct
553 // generate name getter
554 generateMessageNameGetter(w, name, msg.Name)
556 // generate CRC getter
557 generateCrcGetter(w, name, msg.CRC)
559 // generate message type getter method
560 generateMessageTypeGetter(w, name, msgType)
565 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
568 fieldName := strings.TrimPrefix(field.Name, "_")
569 fieldName = camelCaseName(fieldName)
571 // generate length field for strings
572 if field.Type == "string" {
573 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
576 dataType := convertToGoType(ctx, field.Type)
577 fieldType := dataType
579 // check if it is array
580 if field.Length > 0 || field.SizeFrom != "" {
581 if dataType == "uint8" {
584 fieldType = "[]" + dataType
586 fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
588 fieldTags := map[string]string{}
590 if field.Length > 0 {
592 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
594 for _, f := range fields {
595 if f.SizeFrom == field.Name {
596 // variable sized array
597 sizeOfName := camelCaseName(f.Name)
598 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
603 if ctx.includeBinapiNames {
604 fieldTags["binapi"] = field.Name
606 if field.Meta.Limit > 0 {
607 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
610 if len(fieldTags) > 0 {
611 fmt.Fprintf(w, "\t`")
613 for k := range fieldTags {
614 keys = append(keys, k)
618 for _, tt := range keys {
619 t, ok := fieldTags[tt]
627 fmt.Fprintf(w, `%s:"%s"`, tt, t)
635 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
636 fmt.Fprintf(w, `func (*%s) GetMessageName() string {
639 `, structName, msgName)
642 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
643 fmt.Fprintf(w, `func (*%s) GetTypeName() string {
646 `, structName, msgName)
649 func generateCrcGetter(w io.Writer, structName, crc string) {
650 crc = strings.TrimPrefix(crc, "0x")
651 fmt.Fprintf(w, `func (*%s) GetCrcString() string {
657 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
658 fmt.Fprintln(w, "func (*"+structName+") GetMessageType() api.MessageType {")
659 if msgType == requestMessage {
660 fmt.Fprintln(w, "\treturn api.RequestMessage")
661 } else if msgType == replyMessage {
662 fmt.Fprintln(w, "\treturn api.ReplyMessage")
663 } else if msgType == eventMessage {
664 fmt.Fprintln(w, "\treturn api.EventMessage")
666 fmt.Fprintln(w, "\treturn api.OtherMessage")
671 func generateServices(ctx *context, w io.Writer, services []Service) {
673 // generate services comment
674 generateComment(ctx, w, serviceApiName, "services", "service")
676 // generate service api
677 fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
678 for _, svc := range services {
679 generateServiceMethod(ctx, w, &svc)
685 // generate client implementation
686 fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
687 fmt.Fprintf(w, "\tch api.Channel\n")
691 // generate client constructor
692 fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
693 fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
697 for _, svc := range services {
698 method := camelCaseName(svc.RequestType)
699 if m := strings.TrimSuffix(method, "Dump"); method != m {
703 fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
704 generateServiceMethod(ctx, w, &svc)
705 fmt.Fprintln(w, " {")
707 streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
708 fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
709 fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
710 fmt.Fprintf(w, "\treturn x, nil\n")
711 } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
712 fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
713 fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
714 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
715 fmt.Fprintf(w, "\treturn out, nil\n")
717 fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
718 fmt.Fprintf(w, "\treturn nil\n")
724 replyTyp := camelCaseName(svc.ReplyType)
725 method := camelCaseName(svc.RequestType)
726 if m := strings.TrimSuffix(method, "Dump"); method != m {
729 streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
731 fmt.Fprintf(w, "type %s interface {\n", streamApi)
732 fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
736 streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
737 fmt.Fprintf(w, "type %s struct {\n", streamImpl)
738 fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
742 fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
743 fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
744 fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
745 fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
746 fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
747 fmt.Fprintf(w, "\treturn m, nil\n")
756 func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
757 reqTyp := camelCaseName(svc.RequestType)
759 // method name is same as parameter type name by default
762 // use Dump as prefix instead of suffix for stream services
763 if m := strings.TrimSuffix(method, "Dump"); method != m {
768 params := fmt.Sprintf("in *%s", reqTyp)
771 if replyType := camelCaseName(svc.ReplyType); replyType != "" {
774 replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
776 replyTyp = fmt.Sprintf("*%s", replyType)
778 returns = fmt.Sprintf("(%s, error)", replyTyp)
781 fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)