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"
29 objMessages = "messages"
32 objServices = "services"
33 objAliases = "aliases"
34 vlAPIVersion = "vl_api_version"
37 // various object fields
40 msgIdField = "_vl_msg_id"
42 clientIndexField = "client_index"
43 contextField = "context"
45 aliasLengthField = "length"
46 aliasTypeField = "type"
49 streamField = "stream"
50 eventsField = "events"
55 serviceEventPrefix = "want_"
56 serviceDumpSuffix = "_dump"
57 serviceDetailsSuffix = "_details"
58 serviceReplySuffix = "_reply"
59 serviceNoReply = "null"
62 // parsePackage parses provided JSON data into objects prepared for code generation
63 func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
64 logf(" %s (version: %s) contains: %d services, %d messages, %d types, %d enums, %d unions, %d aliases",
66 jsonRoot.Map(vlAPIVersion).Get(),
67 jsonRoot.Map(objServices).Len(),
68 jsonRoot.Map(objMessages).Len(),
69 jsonRoot.Map(objTypes).Len(),
70 jsonRoot.Map(objEnums).Len(),
71 jsonRoot.Map(objUnions).Len(),
72 jsonRoot.Map(objAliases).Len(),
76 APIVersion: jsonRoot.Map(vlAPIVersion).Get().(string),
77 RefMap: make(map[string]string),
81 enums := jsonRoot.Map(objEnums)
82 pkg.Enums = make([]Enum, enums.Len())
83 for i := 0; i < enums.Len(); i++ {
84 enumNode := enums.At(i)
86 enum, err := parseEnum(ctx, enumNode)
91 pkg.RefMap[toApiType(enum.Name)] = enum.Name
94 sort.SliceStable(pkg.Enums, func(i, j int) bool {
95 return pkg.Enums[i].Name < pkg.Enums[j].Name
99 aliases := jsonRoot.Map(objAliases)
100 if aliases.GetType() == jsongo.TypeMap {
101 pkg.Aliases = make([]Alias, aliases.Len())
102 for i, key := range aliases.GetKeys() {
103 aliasNode := aliases.At(key)
105 alias, err := parseAlias(ctx, key.(string), aliasNode)
109 pkg.Aliases[i] = *alias
110 pkg.RefMap[toApiType(alias.Name)] = alias.Name
113 // sort aliases to ensure consistent order
114 sort.Slice(pkg.Aliases, func(i, j int) bool {
115 return pkg.Aliases[i].Name < pkg.Aliases[j].Name
119 types := jsonRoot.Map(objTypes)
120 pkg.Types = make([]Type, types.Len())
121 for i := 0; i < types.Len(); i++ {
122 typNode := types.At(i)
124 typ, err := parseType(ctx, typNode)
129 pkg.RefMap[toApiType(typ.Name)] = typ.Name
132 sort.SliceStable(pkg.Types, func(i, j int) bool {
133 return pkg.Types[i].Name < pkg.Types[j].Name
137 unions := jsonRoot.Map(objUnions)
138 pkg.Unions = make([]Union, unions.Len())
139 for i := 0; i < unions.Len(); i++ {
140 unionNode := unions.At(i)
142 union, err := parseUnion(ctx, unionNode)
146 pkg.Unions[i] = *union
147 pkg.RefMap[toApiType(union.Name)] = union.Name
150 sort.SliceStable(pkg.Unions, func(i, j int) bool {
151 return pkg.Unions[i].Name < pkg.Unions[j].Name
155 messages := jsonRoot.Map(objMessages)
156 pkg.Messages = make([]Message, messages.Len())
157 for i := 0; i < messages.Len(); i++ {
158 msgNode := messages.At(i)
160 msg, err := parseMessage(ctx, msgNode)
164 pkg.Messages[i] = *msg
167 sort.SliceStable(pkg.Messages, func(i, j int) bool {
168 return pkg.Messages[i].Name < pkg.Messages[j].Name
172 services := jsonRoot.Map(objServices)
173 if services.GetType() == jsongo.TypeMap {
174 pkg.Services = make([]Service, services.Len())
175 for i, key := range services.GetKeys() {
176 svcNode := services.At(key)
178 svc, err := parseService(ctx, key.(string), svcNode)
182 pkg.Services[i] = *svc
186 sort.Slice(pkg.Services, func(i, j int) bool {
188 if pkg.Services[i].Stream != pkg.Services[j].Stream {
189 return pkg.Services[i].Stream
191 return pkg.Services[i].RequestType < pkg.Services[j].RequestType
199 // printPackage prints all loaded objects for package
200 func printPackage(pkg *Package) {
201 if len(pkg.Enums) > 0 {
202 logf("loaded %d enums:", len(pkg.Enums))
203 for k, enum := range pkg.Enums {
204 logf(" - enum #%d\t%+v", k, enum)
207 if len(pkg.Unions) > 0 {
208 logf("loaded %d unions:", len(pkg.Unions))
209 for k, union := range pkg.Unions {
210 logf(" - union #%d\t%+v", k, union)
213 if len(pkg.Types) > 0 {
214 logf("loaded %d types:", len(pkg.Types))
215 for _, typ := range pkg.Types {
216 logf(" - type: %q (%d fields)", typ.Name, len(typ.Fields))
219 if len(pkg.Messages) > 0 {
220 logf("loaded %d messages:", len(pkg.Messages))
221 for _, msg := range pkg.Messages {
222 logf(" - message: %q (%d fields)", msg.Name, len(msg.Fields))
225 if len(pkg.Services) > 0 {
226 logf("loaded %d services:", len(pkg.Services))
227 for _, svc := range pkg.Services {
231 } else if len(svc.Events) > 0 {
232 info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
234 logf(" - service: %q -> %q %s", svc.RequestType, svc.ReplyType, info)
239 // parseEnum parses VPP binary API enum object from JSON node
240 func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
241 if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
242 return nil, errors.New("invalid JSON for enum specified")
245 enumName, ok := enumNode.At(0).Get().(string)
247 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
249 enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
251 return nil, fmt.Errorf("enum type invalid or missing")
259 // loop through enum entries, skip first (name) and last (enumtype)
260 for j := 1; j < enumNode.Len()-1; j++ {
261 if enumNode.At(j).GetType() == jsongo.TypeArray {
262 entry := enumNode.At(j)
264 if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue {
265 return nil, errors.New("invalid JSON for enum entry specified")
268 entryName, ok := entry.At(0).Get().(string)
270 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
272 entryVal := entry.At(1).Get()
274 enum.Entries = append(enum.Entries, EnumEntry{
284 // parseUnion parses VPP binary API union object from JSON node
285 func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
286 if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
287 return nil, errors.New("invalid JSON for union specified")
290 unionName, ok := unionNode.At(0).Get().(string)
292 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
294 unionCRC, ok := unionNode.At(unionNode.Len() - 1).At(crcField).Get().(string)
296 return nil, fmt.Errorf("union crc invalid or missing")
304 // loop through union fields, skip first (name) and last (crc)
305 for j := 1; j < unionNode.Len()-1; j++ {
306 if unionNode.At(j).GetType() == jsongo.TypeArray {
307 fieldNode := unionNode.At(j)
309 field, err := parseField(ctx, fieldNode)
314 union.Fields = append(union.Fields, *field)
321 // parseType parses VPP binary API type object from JSON node
322 func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
323 if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
324 return nil, errors.New("invalid JSON for type specified")
327 typeName, ok := typeNode.At(0).Get().(string)
329 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
331 typeCRC, ok := typeNode.At(typeNode.Len() - 1).At(crcField).Get().(string)
333 return nil, fmt.Errorf("type crc invalid or missing")
341 // loop through type fields, skip first (name) and last (crc)
342 for j := 1; j < typeNode.Len()-1; j++ {
343 if typeNode.At(j).GetType() == jsongo.TypeArray {
344 fieldNode := typeNode.At(j)
346 field, err := parseField(ctx, fieldNode)
351 typ.Fields = append(typ.Fields, *field)
358 // parseAlias parses VPP binary API alias object from JSON node
359 func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Alias, error) {
360 if aliasNode.Len() == 0 || aliasNode.At(aliasTypeField).GetType() != jsongo.TypeValue {
361 return nil, errors.New("invalid JSON for alias specified")
368 if typeNode := aliasNode.At(aliasTypeField); typeNode.GetType() == jsongo.TypeValue {
369 typ, ok := typeNode.Get().(string)
371 return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
378 if lengthNode := aliasNode.At(aliasLengthField); lengthNode.GetType() == jsongo.TypeValue {
379 length, ok := lengthNode.Get().(float64)
381 return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
383 alias.Length = int(length)
389 // parseMessage parses VPP binary API message object from JSON node
390 func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
391 if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
392 return nil, errors.New("invalid JSON for message specified")
395 msgName, ok := msgNode.At(0).Get().(string)
397 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
399 msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(crcField).Get().(string)
402 return nil, fmt.Errorf("message crc invalid or missing")
410 // loop through message fields, skip first (name) and last (crc)
411 for j := 1; j < msgNode.Len()-1; j++ {
412 if msgNode.At(j).GetType() == jsongo.TypeArray {
413 fieldNode := msgNode.At(j)
415 field, err := parseField(ctx, fieldNode)
420 msg.Fields = append(msg.Fields, *field)
427 // parseField parses VPP binary API object field from JSON node
428 func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
429 if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
430 return nil, errors.New("invalid JSON for field specified")
433 fieldType, ok := field.At(0).Get().(string)
435 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
437 fieldName, ok := field.At(1).Get().(string)
439 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
441 var fieldLength float64
442 if field.Len() >= 3 {
443 fieldLength, ok = field.At(2).Get().(float64)
445 return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
448 var fieldLengthFrom string
449 if field.Len() >= 4 {
450 fieldLengthFrom, ok = field.At(3).Get().(string)
452 return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
459 Length: int(fieldLength),
460 SizeFrom: fieldLengthFrom,
464 // parseService parses VPP binary API service object from JSON node
465 func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
466 if svcNode.Len() == 0 || svcNode.At(replyField).GetType() != jsongo.TypeValue {
467 return nil, errors.New("invalid JSON for service specified")
471 Name: ctx.moduleName + "." + svcName,
472 RequestType: svcName,
475 if replyNode := svcNode.At(replyField); replyNode.GetType() == jsongo.TypeValue {
476 reply, ok := replyNode.Get().(string)
478 return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
480 if reply != serviceNoReply {
481 svc.ReplyType = reply
485 // stream service (dumps)
486 if streamNode := svcNode.At(streamField); streamNode.GetType() == jsongo.TypeValue {
488 svc.Stream, ok = streamNode.Get().(bool)
490 return nil, fmt.Errorf("service stream is %T, not a string", streamNode.Get())
494 // events service (event subscription)
495 if eventsNode := svcNode.At(eventsField); eventsNode.GetType() == jsongo.TypeArray {
496 for j := 0; j < eventsNode.Len(); j++ {
497 event := eventsNode.At(j).Get().(string)
498 svc.Events = append(svc.Events, event)
503 if len(svc.Events) > 0 {
505 if !strings.HasPrefix(svc.RequestType, serviceEventPrefix) {
506 log.Debugf("unusual EVENTS service: %+v\n"+
507 "- events service %q does not have %q prefix in request.",
508 svc, svc.Name, serviceEventPrefix)
510 } else if svc.Stream {
512 if !strings.HasSuffix(svc.RequestType, serviceDumpSuffix) ||
513 !strings.HasSuffix(svc.ReplyType, serviceDetailsSuffix) {
514 log.Debugf("unusual STREAM service: %+v\n"+
515 "- stream service %q does not have %q suffix in request or reply does not have %q suffix.",
516 svc, svc.Name, serviceDumpSuffix, serviceDetailsSuffix)
518 } else if svc.ReplyType != "" && svc.ReplyType != serviceNoReply {
520 // some messages might have `null` reply (for example: memclnt)
521 if !strings.HasSuffix(svc.ReplyType, serviceReplySuffix) {
522 log.Debugf("unusual REQUEST service: %+v\n"+
523 "- service %q does not have %q suffix in reply.",
524 svc, svc.Name, serviceReplySuffix)