Optimize socketclient adapter and add various code improvements
[govpp.git] / cmd / binapi-generator / parse.go
index 4138ac6..6598b7b 100644 (file)
@@ -21,6 +21,7 @@ import (
        "strings"
 
        "github.com/bennyscetbun/jsongo"
+       "github.com/sirupsen/logrus"
 )
 
 // top level objects
@@ -32,6 +33,7 @@ const (
        objServices  = "services"
        objAliases   = "aliases"
        vlAPIVersion = "vl_api_version"
+       objOptions   = "options"
 )
 
 // various object fields
@@ -59,27 +61,46 @@ const (
        serviceNoReply       = "null"
 )
 
-// parsePackage parses provided JSON data into objects prepared for code generation
-func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
-       logf(" %s (version: %s) contains: %d services, %d messages, %d types, %d enums, %d unions, %d aliases",
-               ctx.packageName,
-               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(),
-       )
+// field meta info
+const (
+       fieldMetaLimit   = "limit"
+       fieldMetaDefault = "default"
+)
+
+// module options
+const (
+       versionOption = "version"
+)
 
+// parsePackage parses provided JSON data into objects prepared for code generation
+func parsePackage(ctx *context, jsonRoot *jsongo.Node) (*Package, error) {
        pkg := Package{
-               APIVersion: jsonRoot.Map(vlAPIVersion).Get().(string),
-               RefMap:     make(map[string]string),
+               Name:    ctx.packageName,
+               RefMap:  make(map[string]string),
+               Imports: map[string]Import{},
+       }
+
+       // parse CRC for API version
+       if crc := jsonRoot.At(vlAPIVersion); crc.GetType() == jsongo.TypeValue {
+               pkg.CRC = crc.Get().(string)
+       }
+
+       // parse version string
+       if opt := jsonRoot.Map(objOptions); opt.GetType() == jsongo.TypeMap {
+               if ver := opt.Map(versionOption); ver.GetType() == jsongo.TypeValue {
+                       pkg.Version = ver.Get().(string)
+               }
+       }
+
+       logf("parsing package %s (version: %s, CRC: %s)", pkg.Name, pkg.Version, pkg.CRC)
+       logf(" consists of:")
+       for _, key := range jsonRoot.GetKeys() {
+               logf("  - %d %s", jsonRoot.At(key).Len(), key)
        }
 
        // parse enums
        enums := jsonRoot.Map(objEnums)
-       pkg.Enums = make([]Enum, enums.Len())
+       pkg.Enums = make([]Enum, 0)
        for i := 0; i < enums.Len(); i++ {
                enumNode := enums.At(i)
 
@@ -87,8 +108,14 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                if err != nil {
                        return nil, err
                }
-               pkg.Enums[i] = *enum
-               pkg.RefMap[toApiType(enum.Name)] = enum.Name
+
+               enumApi := toApiType(enum.Name)
+               if _, ok := pkg.RefMap[enumApi]; ok {
+                       logf("enum %v already known", enumApi)
+                       continue
+               }
+               pkg.RefMap[enumApi] = enum.Name
+               pkg.Enums = append(pkg.Enums, *enum)
        }
        // sort enums
        sort.SliceStable(pkg.Enums, func(i, j int) bool {
@@ -98,16 +125,22 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
        // parse aliases
        aliases := jsonRoot.Map(objAliases)
        if aliases.GetType() == jsongo.TypeMap {
-               pkg.Aliases = make([]Alias, aliases.Len())
-               for i, key := range aliases.GetKeys() {
+               pkg.Aliases = make([]Alias, 0)
+               for _, 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
+
+                       aliasApi := toApiType(alias.Name)
+                       if _, ok := pkg.RefMap[aliasApi]; ok {
+                               logf("alias %v already known", aliasApi)
+                               continue
+                       }
+                       pkg.RefMap[aliasApi] = alias.Name
+                       pkg.Aliases = append(pkg.Aliases, *alias)
                }
        }
        // sort aliases to ensure consistent order
@@ -117,7 +150,7 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
 
        // parse types
        types := jsonRoot.Map(objTypes)
-       pkg.Types = make([]Type, types.Len())
+       pkg.Types = make([]Type, 0)
        for i := 0; i < types.Len(); i++ {
                typNode := types.At(i)
 
@@ -125,8 +158,14 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                if err != nil {
                        return nil, err
                }
-               pkg.Types[i] = *typ
-               pkg.RefMap[toApiType(typ.Name)] = typ.Name
+
+               typApi := toApiType(typ.Name)
+               if _, ok := pkg.RefMap[typApi]; ok {
+                       logf("type %v already known", typApi)
+                       continue
+               }
+               pkg.RefMap[typApi] = typ.Name
+               pkg.Types = append(pkg.Types, *typ)
        }
        // sort types
        sort.SliceStable(pkg.Types, func(i, j int) bool {
@@ -135,7 +174,7 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
 
        // parse unions
        unions := jsonRoot.Map(objUnions)
-       pkg.Unions = make([]Union, unions.Len())
+       pkg.Unions = make([]Union, 0)
        for i := 0; i < unions.Len(); i++ {
                unionNode := unions.At(i)
 
@@ -143,8 +182,14 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
                if err != nil {
                        return nil, err
                }
-               pkg.Unions[i] = *union
-               pkg.RefMap[toApiType(union.Name)] = union.Name
+
+               unionApi := toApiType(union.Name)
+               if _, ok := pkg.RefMap[unionApi]; ok {
+                       logf("union %v already known", unionApi)
+                       continue
+               }
+               pkg.RefMap[unionApi] = union.Name
+               pkg.Unions = append(pkg.Unions, *union)
        }
        // sort unions
        sort.SliceStable(pkg.Unions, func(i, j int) bool {
@@ -196,48 +241,8 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
        return &pkg, nil
 }
 
-// printPackage prints all loaded objects for package
-func printPackage(pkg *Package) {
-       if len(pkg.Enums) > 0 {
-               logf("loaded %d enums:", len(pkg.Enums))
-               for k, enum := range pkg.Enums {
-                       logf(" - enum #%d\t%+v", k, enum)
-               }
-       }
-       if len(pkg.Unions) > 0 {
-               logf("loaded %d unions:", len(pkg.Unions))
-               for k, union := range pkg.Unions {
-                       logf(" - union #%d\t%+v", k, union)
-               }
-       }
-       if len(pkg.Types) > 0 {
-               logf("loaded %d types:", len(pkg.Types))
-               for _, typ := range pkg.Types {
-                       logf(" - type: %q (%d fields)", typ.Name, len(typ.Fields))
-               }
-       }
-       if len(pkg.Messages) > 0 {
-               logf("loaded %d messages:", len(pkg.Messages))
-               for _, msg := range pkg.Messages {
-                       logf(" - message: %q (%d fields)", msg.Name, len(msg.Fields))
-               }
-       }
-       if len(pkg.Services) > 0 {
-               logf("loaded %d services:", len(pkg.Services))
-               for _, svc := range pkg.Services {
-                       var info string
-                       if svc.Stream {
-                               info = "(STREAM)"
-                       } else if len(svc.Events) > 0 {
-                               info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
-                       }
-                       logf(" - service: %q -> %q %s", svc.RequestType, svc.ReplyType, info)
-               }
-       }
-}
-
 // parseEnum parses VPP binary API enum object from JSON node
-func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
+func parseEnum(ctx *context, enumNode *jsongo.Node) (*Enum, error) {
        if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for enum specified")
        }
@@ -282,7 +287,7 @@ func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
 }
 
 // parseUnion parses VPP binary API union object from JSON node
-func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
+func parseUnion(ctx *context, unionNode *jsongo.Node) (*Union, error) {
        if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for union specified")
        }
@@ -291,9 +296,9 @@ 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(crcField).Get().(string)
-       if !ok {
-               return nil, fmt.Errorf("union crc invalid or missing")
+       var unionCRC string
+       if unionNode.At(unionNode.Len()-1).GetType() == jsongo.TypeMap {
+               unionCRC = unionNode.At(unionNode.Len() - 1).At(crcField).Get().(string)
        }
 
        union := Union{
@@ -301,8 +306,8 @@ func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
                CRC:  unionCRC,
        }
 
-       // loop through union fields, skip first (name) and last (crc)
-       for j := 1; j < unionNode.Len()-1; j++ {
+       // loop through union fields, skip first (name)
+       for j := 1; j < unionNode.Len(); j++ {
                if unionNode.At(j).GetType() == jsongo.TypeArray {
                        fieldNode := unionNode.At(j)
 
@@ -319,7 +324,7 @@ func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
 }
 
 // parseType parses VPP binary API type object from JSON node
-func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
+func parseType(ctx *context, typeNode *jsongo.Node) (*Type, error) {
        if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for type specified")
        }
@@ -328,9 +333,9 @@ 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(crcField).Get().(string)
-       if !ok {
-               return nil, fmt.Errorf("type crc invalid or missing")
+       var typeCRC string
+       if lastField := typeNode.At(typeNode.Len() - 1); lastField.GetType() == jsongo.TypeMap {
+               typeCRC = lastField.At(crcField).Get().(string)
        }
 
        typ := Type{
@@ -338,8 +343,8 @@ func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
                CRC:  typeCRC,
        }
 
-       // loop through type fields, skip first (name) and last (crc)
-       for j := 1; j < typeNode.Len()-1; j++ {
+       // loop through type fields, skip first (name)
+       for j := 1; j < typeNode.Len(); j++ {
                if typeNode.At(j).GetType() == jsongo.TypeArray {
                        fieldNode := typeNode.At(j)
 
@@ -356,7 +361,7 @@ func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
 }
 
 // parseAlias parses VPP binary API alias object from JSON node
-func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Alias, error) {
+func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.Node) (*Alias, error) {
        if aliasNode.Len() == 0 || aliasNode.At(aliasTypeField).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for alias specified")
        }
@@ -387,7 +392,7 @@ func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Al
 }
 
 // parseMessage parses VPP binary API message object from JSON node
-func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
+func parseMessage(ctx *context, msgNode *jsongo.Node) (*Message, error) {
        if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for message specified")
        }
@@ -425,7 +430,7 @@ func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
 }
 
 // parseField parses VPP binary API object field from JSON node
-func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
+func parseField(ctx *context, field *jsongo.Node) (*Field, error) {
        if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for field specified")
        }
@@ -438,37 +443,60 @@ func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
        if !ok {
                return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
        }
-       var fieldLength float64
+
+       f := &Field{
+               Name: fieldName,
+               Type: fieldType,
+       }
+
        if field.Len() >= 3 {
-               fieldLength, ok = field.At(2).Get().(float64)
-               if !ok {
-                       return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
+               switch field.At(2).GetType() {
+               case jsongo.TypeValue:
+                       fieldLength, ok := field.At(2).Get().(float64)
+                       if !ok {
+                               return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
+                       }
+                       f.Length = int(fieldLength)
+                       f.SpecifiedLen = true
+
+               case jsongo.TypeMap:
+                       fieldMeta := field.At(2)
+
+                       for _, key := range fieldMeta.GetKeys() {
+                               metaNode := fieldMeta.At(key)
+
+                               switch metaName := key.(string); metaName {
+                               case fieldMetaLimit:
+                                       f.Meta.Limit = int(metaNode.Get().(float64))
+                               case fieldMetaDefault:
+                                       f.Meta.Default = fmt.Sprint(metaNode.Get())
+                               default:
+                                       logrus.Warnf("unknown meta info (%s) for field (%s)", metaName, fieldName)
+                               }
+                       }
+               default:
+                       return nil, errors.New("invalid JSON for field specified")
                }
        }
-       var fieldLengthFrom string
        if field.Len() >= 4 {
-               fieldLengthFrom, ok = field.At(3).Get().(string)
+               fieldLengthFrom, ok := field.At(3).Get().(string)
                if !ok {
                        return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
                }
+               f.SizeFrom = fieldLengthFrom
        }
 
-       return &Field{
-               Name:     fieldName,
-               Type:     fieldType,
-               Length:   int(fieldLength),
-               SizeFrom: fieldLengthFrom,
-       }, nil
+       return f, nil
 }
 
 // parseService parses VPP binary API service object from JSON node
-func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
+func parseService(ctx *context, svcName string, svcNode *jsongo.Node) (*Service, error) {
        if svcNode.Len() == 0 || svcNode.At(replyField).GetType() != jsongo.TypeValue {
                return nil, errors.New("invalid JSON for service specified")
        }
 
        svc := Service{
-               Name:        ctx.moduleName + "." + svcName,
+               Name:        svcName,
                RequestType: svcName,
        }
 
@@ -503,7 +531,7 @@ func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Serv
        if len(svc.Events) > 0 {
                // EVENT service
                if !strings.HasPrefix(svc.RequestType, serviceEventPrefix) {
-                       log.Debugf("unusual EVENTS service: %+v\n"+
+                       logrus.Debugf("unusual EVENTS service: %+v\n"+
                                "- events service %q does not have %q prefix in request.",
                                svc, svc.Name, serviceEventPrefix)
                }
@@ -511,7 +539,7 @@ func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Serv
                // STREAM service
                if !strings.HasSuffix(svc.RequestType, serviceDumpSuffix) ||
                        !strings.HasSuffix(svc.ReplyType, serviceDetailsSuffix) {
-                       log.Debugf("unusual STREAM service: %+v\n"+
+                       logrus.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)
                }
@@ -519,7 +547,7 @@ func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Serv
                // REQUEST service
                // some messages might have `null` reply (for example: memclnt)
                if !strings.HasSuffix(svc.ReplyType, serviceReplySuffix) {
-                       log.Debugf("unusual REQUEST service: %+v\n"+
+                       logrus.Debugf("unusual REQUEST service: %+v\n"+
                                "- service %q does not have %q suffix in reply.",
                                svc, svc.Name, serviceReplySuffix)
                }