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"
26 // parsePackage parses provided JSON data into objects prepared for code generation
27 func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
28 logf(" %s contains: %d services, %d messages, %d types, %d enums, %d unions, %d aliases (version: %s)",
30 jsonRoot.Map("services").Len(),
31 jsonRoot.Map("messages").Len(),
32 jsonRoot.Map("types").Len(),
33 jsonRoot.Map("enums").Len(),
34 jsonRoot.Map("unions").Len(),
35 jsonRoot.Map("aliases").Len(),
36 jsonRoot.Map("vl_api_version").Get(),
40 APIVersion: jsonRoot.Map("vl_api_version").Get().(string),
41 RefMap: make(map[string]string),
45 enums := jsonRoot.Map("enums")
46 pkg.Enums = make([]Enum, enums.Len())
47 for i := 0; i < enums.Len(); i++ {
48 enumNode := enums.At(i)
50 enum, err := parseEnum(ctx, enumNode)
55 pkg.RefMap[toApiType(enum.Name)] = enum.Name
58 sort.SliceStable(pkg.Enums, func(i, j int) bool {
59 return pkg.Enums[i].Name < pkg.Enums[j].Name
63 aliases := jsonRoot.Map("aliases")
64 if aliases.GetType() == jsongo.TypeMap {
65 pkg.Aliases = make([]Alias, aliases.Len())
66 for i, key := range aliases.GetKeys() {
67 aliasNode := aliases.At(key)
69 alias, err := parseAlias(ctx, key.(string), aliasNode)
73 pkg.Aliases[i] = *alias
74 pkg.RefMap[toApiType(alias.Name)] = alias.Name
77 // sort aliases to ensure consistent order
78 sort.Slice(pkg.Aliases, func(i, j int) bool {
79 return pkg.Aliases[i].Name < pkg.Aliases[j].Name
83 types := jsonRoot.Map("types")
84 pkg.Types = make([]Type, types.Len())
85 for i := 0; i < types.Len(); i++ {
86 typNode := types.At(i)
88 typ, err := parseType(ctx, typNode)
93 pkg.RefMap[toApiType(typ.Name)] = typ.Name
96 sort.SliceStable(pkg.Types, func(i, j int) bool {
97 return pkg.Types[i].Name < pkg.Types[j].Name
101 unions := jsonRoot.Map("unions")
102 pkg.Unions = make([]Union, unions.Len())
103 for i := 0; i < unions.Len(); i++ {
104 unionNode := unions.At(i)
106 union, err := parseUnion(ctx, unionNode)
110 pkg.Unions[i] = *union
111 pkg.RefMap[toApiType(union.Name)] = union.Name
114 sort.SliceStable(pkg.Unions, func(i, j int) bool {
115 return pkg.Unions[i].Name < pkg.Unions[j].Name
119 messages := jsonRoot.Map("messages")
120 pkg.Messages = make([]Message, messages.Len())
121 for i := 0; i < messages.Len(); i++ {
122 msgNode := messages.At(i)
124 msg, err := parseMessage(ctx, msgNode)
128 pkg.Messages[i] = *msg
131 sort.SliceStable(pkg.Messages, func(i, j int) bool {
132 return pkg.Messages[i].Name < pkg.Messages[j].Name
136 services := jsonRoot.Map("services")
137 if services.GetType() == jsongo.TypeMap {
138 pkg.Services = make([]Service, services.Len())
139 for i, key := range services.GetKeys() {
140 svcNode := services.At(key)
142 svc, err := parseService(ctx, key.(string), svcNode)
146 pkg.Services[i] = *svc
150 sort.Slice(pkg.Services, func(i, j int) bool {
152 if pkg.Services[i].Stream != pkg.Services[j].Stream {
153 return pkg.Services[i].Stream
155 return pkg.Services[i].RequestType < pkg.Services[j].RequestType
163 // printPackage prints all loaded objects for package
164 func printPackage(pkg *Package) {
165 if len(pkg.Enums) > 0 {
166 logf("loaded %d enums:", len(pkg.Enums))
167 for k, enum := range pkg.Enums {
168 logf(" - enum #%d\t%+v", k, enum)
171 if len(pkg.Unions) > 0 {
172 logf("loaded %d unions:", len(pkg.Unions))
173 for k, union := range pkg.Unions {
174 logf(" - union #%d\t%+v", k, union)
177 if len(pkg.Types) > 0 {
178 logf("loaded %d types:", len(pkg.Types))
179 for _, typ := range pkg.Types {
180 logf(" - type: %q (%d fields)", typ.Name, len(typ.Fields))
183 if len(pkg.Messages) > 0 {
184 logf("loaded %d messages:", len(pkg.Messages))
185 for _, msg := range pkg.Messages {
186 logf(" - message: %q (%d fields)", msg.Name, len(msg.Fields))
189 if len(pkg.Services) > 0 {
190 logf("loaded %d services:", len(pkg.Services))
191 for _, svc := range pkg.Services {
195 } else if len(svc.Events) > 0 {
196 info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
198 logf(" - service: %q -> %q %s", svc.RequestType, svc.ReplyType, info)
203 // parseEnum parses VPP binary API enum object from JSON node
204 func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
205 if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
206 return nil, errors.New("invalid JSON for enum specified")
209 enumName, ok := enumNode.At(0).Get().(string)
211 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
213 enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
215 return nil, fmt.Errorf("enum type invalid or missing")
223 // loop through enum entries, skip first (name) and last (enumtype)
224 for j := 1; j < enumNode.Len()-1; j++ {
225 if enumNode.At(j).GetType() == jsongo.TypeArray {
226 entry := enumNode.At(j)
228 if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue {
229 return nil, errors.New("invalid JSON for enum entry specified")
232 entryName, ok := entry.At(0).Get().(string)
234 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
236 entryVal := entry.At(1).Get()
238 enum.Entries = append(enum.Entries, EnumEntry{
248 // parseUnion parses VPP binary API union object from JSON node
249 func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
250 if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
251 return nil, errors.New("invalid JSON for union specified")
254 unionName, ok := unionNode.At(0).Get().(string)
256 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
258 unionCRC, ok := unionNode.At(unionNode.Len() - 1).At("crc").Get().(string)
260 return nil, fmt.Errorf("union crc invalid or missing")
268 // loop through union fields, skip first (name) and last (crc)
269 for j := 1; j < unionNode.Len()-1; j++ {
270 if unionNode.At(j).GetType() == jsongo.TypeArray {
271 fieldNode := unionNode.At(j)
273 field, err := parseField(ctx, fieldNode)
278 union.Fields = append(union.Fields, *field)
285 // parseType parses VPP binary API type object from JSON node
286 func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
287 if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
288 return nil, errors.New("invalid JSON for type specified")
291 typeName, ok := typeNode.At(0).Get().(string)
293 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
295 typeCRC, ok := typeNode.At(typeNode.Len() - 1).At("crc").Get().(string)
297 return nil, fmt.Errorf("type crc invalid or missing")
305 // loop through type fields, skip first (name) and last (crc)
306 for j := 1; j < typeNode.Len()-1; j++ {
307 if typeNode.At(j).GetType() == jsongo.TypeArray {
308 fieldNode := typeNode.At(j)
310 field, err := parseField(ctx, fieldNode)
315 typ.Fields = append(typ.Fields, *field)
323 aliasesLength = "length"
327 // parseAlias parses VPP binary API alias object from JSON node
328 func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Alias, error) {
329 if aliasNode.Len() == 0 || aliasNode.At(aliasesType).GetType() != jsongo.TypeValue {
330 return nil, errors.New("invalid JSON for alias specified")
337 if typeNode := aliasNode.At(aliasesType); typeNode.GetType() == jsongo.TypeValue {
338 typ, ok := typeNode.Get().(string)
340 return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
347 if lengthNode := aliasNode.At(aliasesLength); lengthNode.GetType() == jsongo.TypeValue {
348 length, ok := lengthNode.Get().(float64)
350 return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
352 alias.Length = int(length)
358 // parseMessage parses VPP binary API message object from JSON node
359 func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
360 if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
361 return nil, errors.New("invalid JSON for message specified")
364 msgName, ok := msgNode.At(0).Get().(string)
366 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
368 msgCRC, ok := msgNode.At(msgNode.Len() - 1).At("crc").Get().(string)
371 return nil, fmt.Errorf("message crc invalid or missing")
379 // loop through message fields, skip first (name) and last (crc)
380 for j := 1; j < msgNode.Len()-1; j++ {
381 if msgNode.At(j).GetType() == jsongo.TypeArray {
382 fieldNode := msgNode.At(j)
384 field, err := parseField(ctx, fieldNode)
389 msg.Fields = append(msg.Fields, *field)
396 // parseField parses VPP binary API object field from JSON node
397 func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
398 if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
399 return nil, errors.New("invalid JSON for field specified")
402 fieldType, ok := field.At(0).Get().(string)
404 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
406 fieldName, ok := field.At(1).Get().(string)
408 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
410 var fieldLength float64
411 if field.Len() >= 3 {
412 fieldLength, ok = field.At(2).Get().(float64)
414 return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
417 var fieldLengthFrom string
418 if field.Len() >= 4 {
419 fieldLengthFrom, ok = field.At(3).Get().(string)
421 return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
428 Length: int(fieldLength),
429 SizeFrom: fieldLengthFrom,
433 // parseService parses VPP binary API service object from JSON node
434 func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
435 if svcNode.Len() == 0 || svcNode.At("reply").GetType() != jsongo.TypeValue {
436 return nil, errors.New("invalid JSON for service specified")
440 Name: ctx.moduleName + "." + svcName,
441 RequestType: svcName,
444 if replyNode := svcNode.At("reply"); replyNode.GetType() == jsongo.TypeValue {
445 reply, ok := replyNode.Get().(string)
447 return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
450 svc.ReplyType = reply
454 // stream service (dumps)
455 if streamNode := svcNode.At("stream"); streamNode.GetType() == jsongo.TypeValue {
457 svc.Stream, ok = streamNode.Get().(bool)
459 return nil, fmt.Errorf("service stream is %T, not a string", streamNode.Get())
463 // events service (event subscription)
464 if eventsNode := svcNode.At("events"); eventsNode.GetType() == jsongo.TypeArray {
465 for j := 0; j < eventsNode.Len(); j++ {
466 event := eventsNode.At(j).Get().(string)
467 svc.Events = append(svc.Events, event)
472 if svc.IsEventService() {
473 if !strings.HasPrefix(svc.RequestType, "want_") {
474 log.Debugf("Unusual EVENTS SERVICE: %+v\n"+
475 "- events service %q does not have 'want_' prefix in request.",
478 } else if svc.IsDumpService() {
479 if !strings.HasSuffix(svc.RequestType, "_dump") ||
480 !strings.HasSuffix(svc.ReplyType, "_details") {
481 log.Debugf("Unusual STREAM SERVICE: %+v\n"+
482 "- stream service %q does not have '_dump' suffix in request or reply does not have '_details' suffix.",
485 } else if svc.IsRequestService() {
486 if !strings.HasSuffix(svc.ReplyType, "_reply") {
487 log.Debugf("Unusual REQUEST SERVICE: %+v\n"+
488 "- service %q does not have '_reply' suffix in reply.",
496 // toApiType returns name that is used as type reference in VPP binary API
497 func toApiType(name string) string {
498 return fmt.Sprintf("vl_api_%s_t", name)
501 // convertToGoType translates the VPP binary API type into Go type
502 func convertToGoType(ctx *context, binapiType string) (typ string) {
503 if t, ok := binapiTypes[binapiType]; ok {
506 } else if r, ok := ctx.packageData.RefMap[binapiType]; ok {
507 // specific types (enums/types/unions)
508 typ = camelCaseName(r)
511 case "bool", "string":
515 log.Warnf("found unknown VPP binary API type %q, using byte", binapiType)
522 func getSizeOfType(typ *Type) (size int) {
523 for _, field := range typ.Fields {
524 size += getSizeOfBinapiTypeLength(field.Type, field.Length)
529 func getSizeOfBinapiTypeLength(typ string, length int) (size int) {
530 if n := getBinapiTypeSize(typ); n > 0 {
540 func getTypeByRef(ctx *context, ref string) *Type {
541 for _, typ := range ctx.packageData.Types {
542 if ref == toApiType(typ.Name) {
549 func getAliasByRef(ctx *context, ref string) *Alias {
550 for _, alias := range ctx.packageData.Aliases {
551 if ref == toApiType(alias.Name) {
558 func getUnionSize(ctx *context, union *Union) (maxSize int) {
559 for _, field := range union.Fields {
560 typ := getTypeByRef(ctx, field.Type)
562 if size := getSizeOfType(typ); size > maxSize {
567 alias := getAliasByRef(ctx, field.Type)
569 if size := getSizeOfBinapiTypeLength(alias.Type, alias.Length); size > maxSize {
574 logf("no type or alias found for union %s field type %q", union.Name, field.Type)