"strings"
"github.com/bennyscetbun/jsongo"
+ "github.com/sirupsen/logrus"
)
-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"
+ objOptions = "options"
+)
-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"
-// 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)",
- 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(),
- )
+ aliasLengthField = "length"
+ aliasTypeField = "type"
+ replyField = "reply"
+ streamField = "stream"
+ eventsField = "events"
+)
+
+// service name parts
+const (
+ serviceEventPrefix = "want_"
+ serviceDumpSuffix = "_dump"
+ serviceDetailsSuffix = "_details"
+ serviceReplySuffix = "_reply"
+ serviceNoReply = "null"
+)
+
+// 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("vl_api_version").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("enums")
- pkg.Enums = make([]Enum, enums.Len())
+ enums := jsonRoot.Map(objEnums)
+ pkg.Enums = make([]Enum, 0)
for i := 0; i < enums.Len(); i++ {
enumNode := enums.At(i)
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 {
+ return pkg.Enums[i].Name < pkg.Enums[j].Name
+ })
+
+ // parse aliases
+ aliases := jsonRoot.Map(objAliases)
+ if aliases.GetType() == jsongo.TypeMap {
+ 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
+ }
+
+ 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
+ sort.Slice(pkg.Aliases, func(i, j int) bool {
+ return pkg.Aliases[i].Name < pkg.Aliases[j].Name
+ })
// parse types
- types := jsonRoot.Map("types")
- pkg.Types = make([]Type, types.Len())
+ types := jsonRoot.Map(objTypes)
+ pkg.Types = make([]Type, 0)
for i := 0; i < types.Len(); i++ {
typNode := types.At(i)
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 {
+ return pkg.Types[i].Name < pkg.Types[j].Name
+ })
// parse unions
- unions := jsonRoot.Map("unions")
- pkg.Unions = make([]Union, unions.Len())
+ unions := jsonRoot.Map(objUnions)
+ pkg.Unions = make([]Union, 0)
for i := 0; i < unions.Len(); i++ {
unionNode := unions.At(i)
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 {
+ 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)
}
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() {
}
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)
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")
}
}
// 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")
}
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)
- 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{
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)
}
// 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")
}
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)
- 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{
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)
return &typ, nil
}
+// parseAlias parses VPP binary API alias object from JSON node
+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")
+ }
+
+ 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) {
+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")
}
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")
}
// 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")
}
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 an int", 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) {
- if svcNode.Len() == 0 || svcNode.At("reply").GetType() != jsongo.TypeValue {
+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,
}
- 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 {
}
// 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)
}
// 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) {
+ logrus.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) {
+ 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)
}
- } 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) {
+ logrus.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
-}