1 // Copyright (c) 2018 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.
23 "github.com/bennyscetbun/jsongo"
24 "github.com/sirupsen/logrus"
30 objMessages = "messages"
33 objServices = "services"
34 objAliases = "aliases"
35 vlAPIVersion = "vl_api_version"
36 objOptions = "options"
39 // various object fields
42 msgIdField = "_vl_msg_id"
44 clientIndexField = "client_index"
45 contextField = "context"
47 aliasLengthField = "length"
48 aliasTypeField = "type"
51 streamField = "stream"
52 eventsField = "events"
57 serviceEventPrefix = "want_"
58 serviceDumpSuffix = "_dump"
59 serviceDetailsSuffix = "_details"
60 serviceReplySuffix = "_reply"
61 serviceNoReply = "null"
66 fieldMetaLimit = "limit"
67 fieldMetaDefault = "default"
72 versionOption = "version"
75 // parsePackage parses provided JSON data into objects prepared for code generation
76 func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
78 Name: ctx.packageName,
79 RefMap: make(map[string]string),
82 // parse CRC for API version
83 if crc := jsonRoot.At(vlAPIVersion); crc.GetType() == jsongo.TypeValue {
84 pkg.CRC = crc.Get().(string)
87 // parse version string
88 if opt := jsonRoot.Map(objOptions); opt.GetType() == jsongo.TypeMap {
89 if ver := opt.Map(versionOption); ver.GetType() == jsongo.TypeValue {
90 pkg.Version = ver.Get().(string)
94 logf("parsing package %s (version: %s, CRC: %s)", pkg.Name, pkg.Version, pkg.CRC)
96 for _, key := range jsonRoot.GetKeys() {
97 logf(" - %d %s", jsonRoot.At(key).Len(), key)
101 enums := jsonRoot.Map(objEnums)
102 pkg.Enums = make([]Enum, 0)
103 for i := 0; i < enums.Len(); i++ {
104 enumNode := enums.At(i)
106 enum, err := parseEnum(ctx, enumNode)
111 enumApi := toApiType(enum.Name)
112 if _, ok := pkg.RefMap[enumApi]; ok {
113 logf("enum %v already known", enumApi)
116 pkg.RefMap[enumApi] = enum.Name
117 pkg.Enums = append(pkg.Enums, *enum)
120 sort.SliceStable(pkg.Enums, func(i, j int) bool {
121 return pkg.Enums[i].Name < pkg.Enums[j].Name
125 aliases := jsonRoot.Map(objAliases)
126 if aliases.GetType() == jsongo.TypeMap {
127 pkg.Aliases = make([]Alias, 0)
128 for _, key := range aliases.GetKeys() {
129 aliasNode := aliases.At(key)
131 alias, err := parseAlias(ctx, key.(string), aliasNode)
136 aliasApi := toApiType(alias.Name)
137 if _, ok := pkg.RefMap[aliasApi]; ok {
138 logf("alias %v already known", aliasApi)
141 pkg.RefMap[aliasApi] = alias.Name
142 pkg.Aliases = append(pkg.Aliases, *alias)
145 // sort aliases to ensure consistent order
146 sort.Slice(pkg.Aliases, func(i, j int) bool {
147 return pkg.Aliases[i].Name < pkg.Aliases[j].Name
151 types := jsonRoot.Map(objTypes)
152 pkg.Types = make([]Type, 0)
153 for i := 0; i < types.Len(); i++ {
154 typNode := types.At(i)
156 typ, err := parseType(ctx, typNode)
161 typApi := toApiType(typ.Name)
162 if _, ok := pkg.RefMap[typApi]; ok {
163 logf("type %v already known", typApi)
166 pkg.RefMap[typApi] = typ.Name
167 pkg.Types = append(pkg.Types, *typ)
170 sort.SliceStable(pkg.Types, func(i, j int) bool {
171 return pkg.Types[i].Name < pkg.Types[j].Name
175 unions := jsonRoot.Map(objUnions)
176 pkg.Unions = make([]Union, 0)
177 for i := 0; i < unions.Len(); i++ {
178 unionNode := unions.At(i)
180 union, err := parseUnion(ctx, unionNode)
185 unionApi := toApiType(union.Name)
186 if _, ok := pkg.RefMap[unionApi]; ok {
187 logf("union %v already known", unionApi)
190 pkg.RefMap[unionApi] = union.Name
191 pkg.Unions = append(pkg.Unions, *union)
194 sort.SliceStable(pkg.Unions, func(i, j int) bool {
195 return pkg.Unions[i].Name < pkg.Unions[j].Name
199 messages := jsonRoot.Map(objMessages)
200 pkg.Messages = make([]Message, messages.Len())
201 for i := 0; i < messages.Len(); i++ {
202 msgNode := messages.At(i)
204 msg, err := parseMessage(ctx, msgNode)
208 pkg.Messages[i] = *msg
211 sort.SliceStable(pkg.Messages, func(i, j int) bool {
212 return pkg.Messages[i].Name < pkg.Messages[j].Name
216 services := jsonRoot.Map(objServices)
217 if services.GetType() == jsongo.TypeMap {
218 pkg.Services = make([]Service, services.Len())
219 for i, key := range services.GetKeys() {
220 svcNode := services.At(key)
222 svc, err := parseService(ctx, key.(string), svcNode)
226 pkg.Services[i] = *svc
230 sort.Slice(pkg.Services, func(i, j int) bool {
232 if pkg.Services[i].Stream != pkg.Services[j].Stream {
233 return pkg.Services[i].Stream
235 return pkg.Services[i].RequestType < pkg.Services[j].RequestType
243 // parseEnum parses VPP binary API enum object from JSON node
244 func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
245 if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
246 return nil, errors.New("invalid JSON for enum specified")
249 enumName, ok := enumNode.At(0).Get().(string)
251 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
253 enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
255 return nil, fmt.Errorf("enum type invalid or missing")
263 // loop through enum entries, skip first (name) and last (enumtype)
264 for j := 1; j < enumNode.Len()-1; j++ {
265 if enumNode.At(j).GetType() == jsongo.TypeArray {
266 entry := enumNode.At(j)
268 if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue {
269 return nil, errors.New("invalid JSON for enum entry specified")
272 entryName, ok := entry.At(0).Get().(string)
274 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
276 entryVal := entry.At(1).Get()
278 enum.Entries = append(enum.Entries, EnumEntry{
288 // parseUnion parses VPP binary API union object from JSON node
289 func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
290 if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
291 return nil, errors.New("invalid JSON for union specified")
294 unionName, ok := unionNode.At(0).Get().(string)
296 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
299 if unionNode.At(unionNode.Len()-1).GetType() == jsongo.TypeMap {
300 unionCRC = unionNode.At(unionNode.Len() - 1).At(crcField).Get().(string)
308 // loop through union fields, skip first (name)
309 for j := 1; j < unionNode.Len(); j++ {
310 if unionNode.At(j).GetType() == jsongo.TypeArray {
311 fieldNode := unionNode.At(j)
313 field, err := parseField(ctx, fieldNode)
318 union.Fields = append(union.Fields, *field)
325 // parseType parses VPP binary API type object from JSON node
326 func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
327 if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
328 return nil, errors.New("invalid JSON for type specified")
331 typeName, ok := typeNode.At(0).Get().(string)
333 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
336 if lastField := typeNode.At(typeNode.Len() - 1); lastField.GetType() == jsongo.TypeMap {
337 typeCRC = lastField.At(crcField).Get().(string)
345 // loop through type fields, skip first (name)
346 for j := 1; j < typeNode.Len(); j++ {
347 if typeNode.At(j).GetType() == jsongo.TypeArray {
348 fieldNode := typeNode.At(j)
350 field, err := parseField(ctx, fieldNode)
355 typ.Fields = append(typ.Fields, *field)
362 // parseAlias parses VPP binary API alias object from JSON node
363 func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Alias, error) {
364 if aliasNode.Len() == 0 || aliasNode.At(aliasTypeField).GetType() != jsongo.TypeValue {
365 return nil, errors.New("invalid JSON for alias specified")
372 if typeNode := aliasNode.At(aliasTypeField); typeNode.GetType() == jsongo.TypeValue {
373 typ, ok := typeNode.Get().(string)
375 return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
382 if lengthNode := aliasNode.At(aliasLengthField); lengthNode.GetType() == jsongo.TypeValue {
383 length, ok := lengthNode.Get().(float64)
385 return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
387 alias.Length = int(length)
393 // parseMessage parses VPP binary API message object from JSON node
394 func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
395 if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
396 return nil, errors.New("invalid JSON for message specified")
399 msgName, ok := msgNode.At(0).Get().(string)
401 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
403 msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(crcField).Get().(string)
406 return nil, fmt.Errorf("message crc invalid or missing")
414 // loop through message fields, skip first (name) and last (crc)
415 for j := 1; j < msgNode.Len()-1; j++ {
416 if msgNode.At(j).GetType() == jsongo.TypeArray {
417 fieldNode := msgNode.At(j)
419 field, err := parseField(ctx, fieldNode)
424 msg.Fields = append(msg.Fields, *field)
431 // parseField parses VPP binary API object field from JSON node
432 func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
433 if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
434 return nil, errors.New("invalid JSON for field specified")
437 fieldType, ok := field.At(0).Get().(string)
439 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
441 fieldName, ok := field.At(1).Get().(string)
443 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
451 if field.Len() >= 3 {
452 switch field.At(2).GetType() {
453 case jsongo.TypeValue:
454 fieldLength, ok := field.At(2).Get().(float64)
456 return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
458 f.Length = int(fieldLength)
459 f.SpecifiedLen = true
462 fieldMeta := field.At(2)
464 for _, key := range fieldMeta.GetKeys() {
465 metaNode := fieldMeta.At(key)
467 switch metaName := key.(string); metaName {
469 f.Meta.Limit = int(metaNode.Get().(float64))
470 case fieldMetaDefault:
471 f.Meta.Default = metaNode.Get().(float64)
473 logrus.Warnf("unknown meta info (%s) for field (%s)", metaName, fieldName)
477 return nil, errors.New("invalid JSON for field specified")
480 if field.Len() >= 4 {
481 fieldLengthFrom, ok := field.At(3).Get().(string)
483 return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
485 f.SizeFrom = fieldLengthFrom
491 // parseService parses VPP binary API service object from JSON node
492 func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
493 if svcNode.Len() == 0 || svcNode.At(replyField).GetType() != jsongo.TypeValue {
494 return nil, errors.New("invalid JSON for service specified")
499 RequestType: svcName,
502 if replyNode := svcNode.At(replyField); replyNode.GetType() == jsongo.TypeValue {
503 reply, ok := replyNode.Get().(string)
505 return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
507 if reply != serviceNoReply {
508 svc.ReplyType = reply
512 // stream service (dumps)
513 if streamNode := svcNode.At(streamField); streamNode.GetType() == jsongo.TypeValue {
515 svc.Stream, ok = streamNode.Get().(bool)
517 return nil, fmt.Errorf("service stream is %T, not a string", streamNode.Get())
521 // events service (event subscription)
522 if eventsNode := svcNode.At(eventsField); eventsNode.GetType() == jsongo.TypeArray {
523 for j := 0; j < eventsNode.Len(); j++ {
524 event := eventsNode.At(j).Get().(string)
525 svc.Events = append(svc.Events, event)
530 if len(svc.Events) > 0 {
532 if !strings.HasPrefix(svc.RequestType, serviceEventPrefix) {
533 logrus.Debugf("unusual EVENTS service: %+v\n"+
534 "- events service %q does not have %q prefix in request.",
535 svc, svc.Name, serviceEventPrefix)
537 } else if svc.Stream {
539 if !strings.HasSuffix(svc.RequestType, serviceDumpSuffix) ||
540 !strings.HasSuffix(svc.ReplyType, serviceDetailsSuffix) {
541 logrus.Debugf("unusual STREAM service: %+v\n"+
542 "- stream service %q does not have %q suffix in request or reply does not have %q suffix.",
543 svc, svc.Name, serviceDumpSuffix, serviceDetailsSuffix)
545 } else if svc.ReplyType != "" && svc.ReplyType != serviceNoReply {
547 // some messages might have `null` reply (for example: memclnt)
548 if !strings.HasSuffix(svc.ReplyType, serviceReplySuffix) {
549 logrus.Debugf("unusual REQUEST service: %+v\n"+
550 "- service %q does not have %q suffix in reply.",
551 svc, svc.Name, serviceReplySuffix)