Generator improvements and cleanup
[govpp.git] / cmd / binapi-generator / parse.go
index 07abebd..4138ac6 100644 (file)
@@ -23,26 +23,62 @@ import (
        "github.com/bennyscetbun/jsongo"
 )
 
+// top level objects
+const (
+       objTypes     = "types"
+       objMessages  = "messages"
+       objUnions    = "unions"
+       objEnums     = "enums"
+       objServices  = "services"
+       objAliases   = "aliases"
+       vlAPIVersion = "vl_api_version"
+)
+
+// various object fields
+const (
+       crcField   = "crc"
+       msgIdField = "_vl_msg_id"
+
+       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, %d aliases (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("aliases").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)
@@ -60,7 +96,7 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
        })
 
        // parse aliases
-       aliases := jsonRoot.Map("aliases")
+       aliases := jsonRoot.Map(objAliases)
        if aliases.GetType() == jsongo.TypeMap {
                pkg.Aliases = make([]Alias, aliases.Len())
                for i, key := range aliases.GetKeys() {
@@ -80,7 +116,7 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
        })
 
        // 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)
@@ -98,7 +134,7 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
        })
 
        // 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)
@@ -116,7 +152,7 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
        })
 
        // 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)
@@ -133,7 +169,7 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
        })
 
        // 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() {
@@ -255,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")
        }
@@ -292,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")
        }
@@ -319,14 +355,9 @@ func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
        return &typ, nil
 }
 
-const (
-       aliasesLength = "length"
-       aliasesType   = "type"
-)
-
 // 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(aliasesType).GetType() != jsongo.TypeValue {
+       if aliasNode.Len() == 0 || aliasNode.At(aliasTypeField).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for alias specified")
        }
 
@@ -334,7 +365,7 @@ func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Al
                Name: aliasName,
        }
 
-       if typeNode := aliasNode.At(aliasesType); typeNode.GetType() == jsongo.TypeValue {
+       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())
@@ -344,7 +375,7 @@ func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Al
                }
        }
 
-       if lengthNode := aliasNode.At(aliasesLength); lengthNode.GetType() == jsongo.TypeValue {
+       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())
@@ -365,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")
@@ -432,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")
        }
 
@@ -441,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 {
@@ -461,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)
@@ -469,111 +500,30 @@ func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Serv
        }
 
        // validate service
-       if svc.IsEventService() {
-               if !strings.HasPrefix(svc.RequestType, "want_") {
-                       log.Debugf("Unusual EVENTS SERVICE: %+v\n"+
-                               "- events service %q does not have 'want_' prefix in request.",
-                               svc, svc.Name)
-               }
-       } else if svc.IsDumpService() {
-               if !strings.HasSuffix(svc.RequestType, "_dump") ||
-                       !strings.HasSuffix(svc.ReplyType, "_details") {
-                       log.Debugf("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.IsRequestService() {
-               if !strings.HasSuffix(svc.ReplyType, "_reply") {
-                       log.Debugf("Unusual REQUEST SERVICE: %+v\n"+
-                               "- service %q does not have '_reply' suffix in reply.",
-                               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.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.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
 }
-
-// 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)
-}
-
-// 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 {
-               switch binapiType {
-               case "bool", "string":
-                       typ = binapiType
-               default:
-                       // fallback type
-                       log.Warnf("found unknown VPP binary API type %q, using byte", binapiType)
-                       typ = "byte"
-               }
-       }
-       return typ
-}
-
-func getSizeOfType(typ *Type) (size int) {
-       for _, field := range typ.Fields {
-               size += getSizeOfBinapiTypeLength(field.Type, field.Length)
-       }
-       return size
-}
-
-func getSizeOfBinapiTypeLength(typ string, length int) (size int) {
-       if n := getBinapiTypeSize(typ); n > 0 {
-               if length > 0 {
-                       return n * length
-               } else {
-                       return n
-               }
-       }
-       return
-}
-
-func getTypeByRef(ctx *context, ref string) *Type {
-       for _, typ := range ctx.packageData.Types {
-               if ref == toApiType(typ.Name) {
-                       return &typ
-               }
-       }
-       return nil
-}
-
-func getAliasByRef(ctx *context, ref string) *Alias {
-       for _, alias := range ctx.packageData.Aliases {
-               if ref == toApiType(alias.Name) {
-                       return &alias
-               }
-       }
-       return nil
-}
-
-func getUnionSize(ctx *context, union *Union) (maxSize int) {
-       for _, field := range union.Fields {
-               typ := getTypeByRef(ctx, field.Type)
-               if typ != nil {
-                       if size := getSizeOfType(typ); size > maxSize {
-                               maxSize = size
-                       }
-                       continue
-               }
-               alias := getAliasByRef(ctx, field.Type)
-               if alias != nil {
-                       if size := getSizeOfBinapiTypeLength(alias.Type, alias.Length); size > maxSize {
-                               maxSize = size
-                       }
-                       continue
-               } else {
-                       logf("no type or alias found for union %s field type %q", union.Name, field.Type)
-                       continue
-               }
-       }
-       return
-}