Generator improvements and cleanup
[govpp.git] / cmd / binapi-generator / parse.go
index 2d6fdd4..4138ac6 100644 (file)
@@ -23,50 +23,62 @@ import (
        "github.com/bennyscetbun/jsongo"
 )
 
-func getTypeByRef(ctx *context, ref string) *Type {
-       for _, typ := range ctx.packageData.Types {
-               if ref == toApiType(typ.Name) {
-                       return &typ
-               }
-       }
-       return nil
-}
+// top level objects
+const (
+       objTypes     = "types"
+       objMessages  = "messages"
+       objUnions    = "unions"
+       objEnums     = "enums"
+       objServices  = "services"
+       objAliases   = "aliases"
+       vlAPIVersion = "vl_api_version"
+)
 
-func getUnionSize(ctx *context, union *Union) (maxSize int) {
-       for _, field := range union.Fields {
-               if typ := getTypeByRef(ctx, field.Type); typ != nil {
-                       if size := getSizeOfType(typ); size > maxSize {
-                               maxSize = size
-                       }
-               }
-       }
-       return
-}
+// various object fields
+const (
+       crcField   = "crc"
+       msgIdField = "_vl_msg_id"
 
-// toApiType returns name that is used as type reference in VPP binary API
-func toApiType(name string) string {
-       return fmt.Sprintf("vl_api_%s_t", name)
-}
+       clientIndexField = "client_index"
+       contextField     = "context"
+
+       aliasLengthField = "length"
+       aliasTypeField   = "type"
+
+       replyField  = "reply"
+       streamField = "stream"
+       eventsField = "events"
+)
+
+// service name parts
+const (
+       serviceEventPrefix   = "want_"
+       serviceDumpSuffix    = "_dump"
+       serviceDetailsSuffix = "_details"
+       serviceReplySuffix   = "_reply"
+       serviceNoReply       = "null"
+)
 
 // parsePackage parses provided JSON data into objects prepared for code generation
 func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
-       logf(" %s contains: %d services, %d messages, %d types, %d enums, %d unions (version: %s)",
+       logf(" %s (version: %s) contains: %d services, %d messages, %d types, %d enums, %d unions, %d aliases",
                ctx.packageName,
-               jsonRoot.Map("services").Len(),
-               jsonRoot.Map("messages").Len(),
-               jsonRoot.Map("types").Len(),
-               jsonRoot.Map("enums").Len(),
-               jsonRoot.Map("unions").Len(),
-               jsonRoot.Map("vl_api_version").Get(),
+               jsonRoot.Map(vlAPIVersion).Get(),
+               jsonRoot.Map(objServices).Len(),
+               jsonRoot.Map(objMessages).Len(),
+               jsonRoot.Map(objTypes).Len(),
+               jsonRoot.Map(objEnums).Len(),
+               jsonRoot.Map(objUnions).Len(),
+               jsonRoot.Map(objAliases).Len(),
        )
 
        pkg := Package{
-               APIVersion: jsonRoot.Map("vl_api_version").Get().(string),
+               APIVersion: jsonRoot.Map(vlAPIVersion).Get().(string),
                RefMap:     make(map[string]string),
        }
 
        // parse enums
-       enums := jsonRoot.Map("enums")
+       enums := jsonRoot.Map(objEnums)
        pkg.Enums = make([]Enum, enums.Len())
        for i := 0; i < enums.Len(); i++ {
                enumNode := enums.At(i)
@@ -78,9 +90,33 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                pkg.Enums[i] = *enum
                pkg.RefMap[toApiType(enum.Name)] = enum.Name
        }
+       // sort enums
+       sort.SliceStable(pkg.Enums, func(i, j int) bool {
+               return pkg.Enums[i].Name < pkg.Enums[j].Name
+       })
+
+       // parse aliases
+       aliases := jsonRoot.Map(objAliases)
+       if aliases.GetType() == jsongo.TypeMap {
+               pkg.Aliases = make([]Alias, aliases.Len())
+               for i, key := range aliases.GetKeys() {
+                       aliasNode := aliases.At(key)
+
+                       alias, err := parseAlias(ctx, key.(string), aliasNode)
+                       if err != nil {
+                               return nil, err
+                       }
+                       pkg.Aliases[i] = *alias
+                       pkg.RefMap[toApiType(alias.Name)] = alias.Name
+               }
+       }
+       // sort aliases to ensure consistent order
+       sort.Slice(pkg.Aliases, func(i, j int) bool {
+               return pkg.Aliases[i].Name < pkg.Aliases[j].Name
+       })
 
        // parse types
-       types := jsonRoot.Map("types")
+       types := jsonRoot.Map(objTypes)
        pkg.Types = make([]Type, types.Len())
        for i := 0; i < types.Len(); i++ {
                typNode := types.At(i)
@@ -92,9 +128,13 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                pkg.Types[i] = *typ
                pkg.RefMap[toApiType(typ.Name)] = typ.Name
        }
+       // sort types
+       sort.SliceStable(pkg.Types, func(i, j int) bool {
+               return pkg.Types[i].Name < pkg.Types[j].Name
+       })
 
        // parse unions
-       unions := jsonRoot.Map("unions")
+       unions := jsonRoot.Map(objUnions)
        pkg.Unions = make([]Union, unions.Len())
        for i := 0; i < unions.Len(); i++ {
                unionNode := unions.At(i)
@@ -106,9 +146,13 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                pkg.Unions[i] = *union
                pkg.RefMap[toApiType(union.Name)] = union.Name
        }
+       // sort unions
+       sort.SliceStable(pkg.Unions, func(i, j int) bool {
+               return pkg.Unions[i].Name < pkg.Unions[j].Name
+       })
 
        // parse messages
-       messages := jsonRoot.Map("messages")
+       messages := jsonRoot.Map(objMessages)
        pkg.Messages = make([]Message, messages.Len())
        for i := 0; i < messages.Len(); i++ {
                msgNode := messages.At(i)
@@ -119,9 +163,13 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                }
                pkg.Messages[i] = *msg
        }
+       // sort messages
+       sort.SliceStable(pkg.Messages, func(i, j int) bool {
+               return pkg.Messages[i].Name < pkg.Messages[j].Name
+       })
 
        // parse services
-       services := jsonRoot.Map("services")
+       services := jsonRoot.Map(objServices)
        if services.GetType() == jsongo.TypeMap {
                pkg.Services = make([]Service, services.Len())
                for i, key := range services.GetKeys() {
@@ -133,16 +181,15 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                        }
                        pkg.Services[i] = *svc
                }
-
-               // sort services
-               sort.Slice(pkg.Services, func(i, j int) bool {
-                       // dumps first
-                       if pkg.Services[i].Stream != pkg.Services[j].Stream {
-                               return pkg.Services[i].Stream
-                       }
-                       return pkg.Services[i].RequestType < pkg.Services[j].RequestType
-               })
        }
+       // sort services
+       sort.Slice(pkg.Services, func(i, j int) bool {
+               // dumps first
+               if pkg.Services[i].Stream != pkg.Services[j].Stream {
+                       return pkg.Services[i].Stream
+               }
+               return pkg.Services[i].RequestType < pkg.Services[j].RequestType
+       })
 
        printPackage(&pkg)
 
@@ -244,7 +291,7 @@ func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
        if !ok {
                return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
        }
-       unionCRC, ok := unionNode.At(unionNode.Len() - 1).At("crc").Get().(string)
+       unionCRC, ok := unionNode.At(unionNode.Len() - 1).At(crcField).Get().(string)
        if !ok {
                return nil, fmt.Errorf("union crc invalid or missing")
        }
@@ -281,7 +328,7 @@ func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
        if !ok {
                return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
        }
-       typeCRC, ok := typeNode.At(typeNode.Len() - 1).At("crc").Get().(string)
+       typeCRC, ok := typeNode.At(typeNode.Len() - 1).At(crcField).Get().(string)
        if !ok {
                return nil, fmt.Errorf("type crc invalid or missing")
        }
@@ -308,6 +355,37 @@ func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
        return &typ, nil
 }
 
+// parseAlias parses VPP binary API alias object from JSON node
+func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Alias, error) {
+       if aliasNode.Len() == 0 || aliasNode.At(aliasTypeField).GetType() != jsongo.TypeValue {
+               return nil, errors.New("invalid JSON for alias specified")
+       }
+
+       alias := Alias{
+               Name: aliasName,
+       }
+
+       if typeNode := aliasNode.At(aliasTypeField); typeNode.GetType() == jsongo.TypeValue {
+               typ, ok := typeNode.Get().(string)
+               if !ok {
+                       return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
+               }
+               if typ != "null" {
+                       alias.Type = typ
+               }
+       }
+
+       if lengthNode := aliasNode.At(aliasLengthField); lengthNode.GetType() == jsongo.TypeValue {
+               length, ok := lengthNode.Get().(float64)
+               if !ok {
+                       return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
+               }
+               alias.Length = int(length)
+       }
+
+       return &alias, nil
+}
+
 // parseMessage parses VPP binary API message object from JSON node
 func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
        if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
@@ -318,7 +396,7 @@ func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
        if !ok {
                return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
        }
-       msgCRC, ok := msgNode.At(msgNode.Len() - 1).At("crc").Get().(string)
+       msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(crcField).Get().(string)
        if !ok {
 
                return nil, fmt.Errorf("message crc invalid or missing")
@@ -364,7 +442,7 @@ func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
        if field.Len() >= 3 {
                fieldLength, ok = field.At(2).Get().(float64)
                if !ok {
-                       return nil, fmt.Errorf("field length is %T, not an int", field.At(2).Get())
+                       return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
                }
        }
        var fieldLengthFrom string
@@ -385,7 +463,7 @@ func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
 
 // parseService parses VPP binary API service object from JSON node
 func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
-       if svcNode.Len() == 0 || svcNode.At("reply").GetType() != jsongo.TypeValue {
+       if svcNode.Len() == 0 || svcNode.At(replyField).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for service specified")
        }
 
@@ -394,18 +472,18 @@ func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Serv
                RequestType: svcName,
        }
 
-       if replyNode := svcNode.At("reply"); replyNode.GetType() == jsongo.TypeValue {
+       if replyNode := svcNode.At(replyField); replyNode.GetType() == jsongo.TypeValue {
                reply, ok := replyNode.Get().(string)
                if !ok {
                        return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
                }
-               if reply != "null" {
+               if reply != serviceNoReply {
                        svc.ReplyType = reply
                }
        }
 
        // stream service (dumps)
-       if streamNode := svcNode.At("stream"); streamNode.GetType() == jsongo.TypeValue {
+       if streamNode := svcNode.At(streamField); streamNode.GetType() == jsongo.TypeValue {
                var ok bool
                svc.Stream, ok = streamNode.Get().(bool)
                if !ok {
@@ -414,7 +492,7 @@ func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Serv
        }
 
        // events service (event subscription)
-       if eventsNode := svcNode.At("events"); eventsNode.GetType() == jsongo.TypeArray {
+       if eventsNode := svcNode.At(eventsField); eventsNode.GetType() == jsongo.TypeArray {
                for j := 0; j < eventsNode.Len(); j++ {
                        event := eventsNode.At(j).Get().(string)
                        svc.Events = append(svc.Events, event)
@@ -422,42 +500,30 @@ func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Serv
        }
 
        // validate service
-       if svc.IsEventService() {
-               if !strings.HasPrefix(svc.RequestType, "want_") {
-                       log.Warnf("Unusual EVENTS SERVICE: %+v\n"+
-                               "- events service %q does not have 'want_' prefix in request.",
-                               svc, svc.Name)
+       if len(svc.Events) > 0 {
+               // EVENT service
+               if !strings.HasPrefix(svc.RequestType, serviceEventPrefix) {
+                       log.Debugf("unusual EVENTS service: %+v\n"+
+                               "- events service %q does not have %q prefix in request.",
+                               svc, svc.Name, serviceEventPrefix)
                }
-       } else if svc.IsDumpService() {
-               if !strings.HasSuffix(svc.RequestType, "_dump") ||
-                       !strings.HasSuffix(svc.ReplyType, "_details") {
-                       log.Warnf("Unusual STREAM SERVICE: %+v\n"+
-                               "- stream service %q does not have '_dump' suffix in request or reply does not have '_details' suffix.",
-                               svc, svc.Name)
+       } else if svc.Stream {
+               // STREAM service
+               if !strings.HasSuffix(svc.RequestType, serviceDumpSuffix) ||
+                       !strings.HasSuffix(svc.ReplyType, serviceDetailsSuffix) {
+                       log.Debugf("unusual STREAM service: %+v\n"+
+                               "- stream service %q does not have %q suffix in request or reply does not have %q suffix.",
+                               svc, svc.Name, serviceDumpSuffix, serviceDetailsSuffix)
                }
-       } else if svc.IsRequestService() {
-               if !strings.HasSuffix(svc.ReplyType, "_reply") {
-                       log.Warnf("Unusual REQUEST SERVICE: %+v\n"+
-                               "- service %q does not have '_reply' suffix in reply.",
-                               svc, svc.Name)
+       } else if svc.ReplyType != "" && svc.ReplyType != serviceNoReply {
+               // REQUEST service
+               // some messages might have `null` reply (for example: memclnt)
+               if !strings.HasSuffix(svc.ReplyType, serviceReplySuffix) {
+                       log.Debugf("unusual REQUEST service: %+v\n"+
+                               "- service %q does not have %q suffix in reply.",
+                               svc, svc.Name, serviceReplySuffix)
                }
        }
 
        return &svc, nil
 }
-
-// convertToGoType translates the VPP binary API type into Go type
-func convertToGoType(ctx *context, binapiType string) (typ string) {
-       if t, ok := binapiTypes[binapiType]; ok {
-               // basic types
-               typ = t
-       } else if r, ok := ctx.packageData.RefMap[binapiType]; ok {
-               // specific types (enums/types/unions)
-               typ = camelCaseName(r)
-       } else {
-               // fallback type
-               log.Warnf("found unknown VPP binary API type %q, using byte", binapiType)
-               typ = "byte"
-       }
-       return typ
-}