5bb3e8ebe03cda5d470c3da2ad7b201ae87c1df4
[govpp.git] / cmd / binapi-generator / parse.go
1 // Copyright (c) 2018 Cisco and/or its affiliates.
2 //
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:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 package main
16
17 import (
18         "errors"
19         "fmt"
20         "sort"
21         "strings"
22
23         "github.com/bennyscetbun/jsongo"
24 )
25
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)",
29                 ctx.packageName,
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(),
37         )
38
39         pkg := Package{
40                 APIVersion: jsonRoot.Map("vl_api_version").Get().(string),
41                 RefMap:     make(map[string]string),
42         }
43
44         // parse enums
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)
49
50                 enum, err := parseEnum(ctx, enumNode)
51                 if err != nil {
52                         return nil, err
53                 }
54                 pkg.Enums[i] = *enum
55                 pkg.RefMap[toApiType(enum.Name)] = enum.Name
56         }
57
58         // parse aliases
59         aliases := jsonRoot.Map("aliases")
60         if aliases.GetType() == jsongo.TypeMap {
61                 pkg.Aliases = make([]Alias, aliases.Len())
62                 for i, key := range aliases.GetKeys() {
63                         aliasNode := aliases.At(key)
64
65                         alias, err := parseAlias(ctx, key.(string), aliasNode)
66                         if err != nil {
67                                 return nil, err
68                         }
69                         pkg.Aliases[i] = *alias
70                         pkg.RefMap[toApiType(alias.Name)] = alias.Name
71                 }
72         }
73         // sort aliases to ensure consistent order
74         sort.Slice(pkg.Aliases, func(i, j int) bool {
75                 return pkg.Aliases[i].Name < pkg.Aliases[j].Name
76         })
77
78         // parse types
79         types := jsonRoot.Map("types")
80         pkg.Types = make([]Type, types.Len())
81         for i := 0; i < types.Len(); i++ {
82                 typNode := types.At(i)
83
84                 typ, err := parseType(ctx, typNode)
85                 if err != nil {
86                         return nil, err
87                 }
88                 pkg.Types[i] = *typ
89                 pkg.RefMap[toApiType(typ.Name)] = typ.Name
90         }
91
92         // parse unions
93         unions := jsonRoot.Map("unions")
94         pkg.Unions = make([]Union, unions.Len())
95         for i := 0; i < unions.Len(); i++ {
96                 unionNode := unions.At(i)
97
98                 union, err := parseUnion(ctx, unionNode)
99                 if err != nil {
100                         return nil, err
101                 }
102                 pkg.Unions[i] = *union
103                 pkg.RefMap[toApiType(union.Name)] = union.Name
104         }
105
106         // parse messages
107         messages := jsonRoot.Map("messages")
108         pkg.Messages = make([]Message, messages.Len())
109         for i := 0; i < messages.Len(); i++ {
110                 msgNode := messages.At(i)
111
112                 msg, err := parseMessage(ctx, msgNode)
113                 if err != nil {
114                         return nil, err
115                 }
116                 pkg.Messages[i] = *msg
117         }
118
119         // parse services
120         services := jsonRoot.Map("services")
121         if services.GetType() == jsongo.TypeMap {
122                 pkg.Services = make([]Service, services.Len())
123                 for i, key := range services.GetKeys() {
124                         svcNode := services.At(key)
125
126                         svc, err := parseService(ctx, key.(string), svcNode)
127                         if err != nil {
128                                 return nil, err
129                         }
130                         pkg.Services[i] = *svc
131                 }
132
133                 // sort services
134                 sort.Slice(pkg.Services, func(i, j int) bool {
135                         // dumps first
136                         if pkg.Services[i].Stream != pkg.Services[j].Stream {
137                                 return pkg.Services[i].Stream
138                         }
139                         return pkg.Services[i].RequestType < pkg.Services[j].RequestType
140                 })
141         }
142
143         printPackage(&pkg)
144
145         return &pkg, nil
146 }
147
148 // printPackage prints all loaded objects for package
149 func printPackage(pkg *Package) {
150         if len(pkg.Enums) > 0 {
151                 logf("loaded %d enums:", len(pkg.Enums))
152                 for k, enum := range pkg.Enums {
153                         logf(" - enum #%d\t%+v", k, enum)
154                 }
155         }
156         if len(pkg.Unions) > 0 {
157                 logf("loaded %d unions:", len(pkg.Unions))
158                 for k, union := range pkg.Unions {
159                         logf(" - union #%d\t%+v", k, union)
160                 }
161         }
162         if len(pkg.Types) > 0 {
163                 logf("loaded %d types:", len(pkg.Types))
164                 for _, typ := range pkg.Types {
165                         logf(" - type: %q (%d fields)", typ.Name, len(typ.Fields))
166                 }
167         }
168         if len(pkg.Messages) > 0 {
169                 logf("loaded %d messages:", len(pkg.Messages))
170                 for _, msg := range pkg.Messages {
171                         logf(" - message: %q (%d fields)", msg.Name, len(msg.Fields))
172                 }
173         }
174         if len(pkg.Services) > 0 {
175                 logf("loaded %d services:", len(pkg.Services))
176                 for _, svc := range pkg.Services {
177                         var info string
178                         if svc.Stream {
179                                 info = "(STREAM)"
180                         } else if len(svc.Events) > 0 {
181                                 info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
182                         }
183                         logf(" - service: %q -> %q %s", svc.RequestType, svc.ReplyType, info)
184                 }
185         }
186 }
187
188 // parseEnum parses VPP binary API enum object from JSON node
189 func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
190         if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
191                 return nil, errors.New("invalid JSON for enum specified")
192         }
193
194         enumName, ok := enumNode.At(0).Get().(string)
195         if !ok {
196                 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
197         }
198         enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
199         if !ok {
200                 return nil, fmt.Errorf("enum type invalid or missing")
201         }
202
203         enum := Enum{
204                 Name: enumName,
205                 Type: enumType,
206         }
207
208         // loop through enum entries, skip first (name) and last (enumtype)
209         for j := 1; j < enumNode.Len()-1; j++ {
210                 if enumNode.At(j).GetType() == jsongo.TypeArray {
211                         entry := enumNode.At(j)
212
213                         if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue {
214                                 return nil, errors.New("invalid JSON for enum entry specified")
215                         }
216
217                         entryName, ok := entry.At(0).Get().(string)
218                         if !ok {
219                                 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
220                         }
221                         entryVal := entry.At(1).Get()
222
223                         enum.Entries = append(enum.Entries, EnumEntry{
224                                 Name:  entryName,
225                                 Value: entryVal,
226                         })
227                 }
228         }
229
230         return &enum, nil
231 }
232
233 // parseUnion parses VPP binary API union object from JSON node
234 func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
235         if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
236                 return nil, errors.New("invalid JSON for union specified")
237         }
238
239         unionName, ok := unionNode.At(0).Get().(string)
240         if !ok {
241                 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
242         }
243         unionCRC, ok := unionNode.At(unionNode.Len() - 1).At("crc").Get().(string)
244         if !ok {
245                 return nil, fmt.Errorf("union crc invalid or missing")
246         }
247
248         union := Union{
249                 Name: unionName,
250                 CRC:  unionCRC,
251         }
252
253         // loop through union fields, skip first (name) and last (crc)
254         for j := 1; j < unionNode.Len()-1; j++ {
255                 if unionNode.At(j).GetType() == jsongo.TypeArray {
256                         fieldNode := unionNode.At(j)
257
258                         field, err := parseField(ctx, fieldNode)
259                         if err != nil {
260                                 return nil, err
261                         }
262
263                         union.Fields = append(union.Fields, *field)
264                 }
265         }
266
267         return &union, nil
268 }
269
270 // parseType parses VPP binary API type object from JSON node
271 func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
272         if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
273                 return nil, errors.New("invalid JSON for type specified")
274         }
275
276         typeName, ok := typeNode.At(0).Get().(string)
277         if !ok {
278                 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
279         }
280         typeCRC, ok := typeNode.At(typeNode.Len() - 1).At("crc").Get().(string)
281         if !ok {
282                 return nil, fmt.Errorf("type crc invalid or missing")
283         }
284
285         typ := Type{
286                 Name: typeName,
287                 CRC:  typeCRC,
288         }
289
290         // loop through type fields, skip first (name) and last (crc)
291         for j := 1; j < typeNode.Len()-1; j++ {
292                 if typeNode.At(j).GetType() == jsongo.TypeArray {
293                         fieldNode := typeNode.At(j)
294
295                         field, err := parseField(ctx, fieldNode)
296                         if err != nil {
297                                 return nil, err
298                         }
299
300                         typ.Fields = append(typ.Fields, *field)
301                 }
302         }
303
304         return &typ, nil
305 }
306
307 const (
308         aliasesLength = "length"
309         aliasesType   = "type"
310 )
311
312 // parseAlias parses VPP binary API alias object from JSON node
313 func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Alias, error) {
314         if aliasNode.Len() == 0 || aliasNode.At(aliasesType).GetType() != jsongo.TypeValue {
315                 return nil, errors.New("invalid JSON for alias specified")
316         }
317
318         alias := Alias{
319                 Name: aliasName,
320         }
321
322         if typeNode := aliasNode.At(aliasesType); typeNode.GetType() == jsongo.TypeValue {
323                 typ, ok := typeNode.Get().(string)
324                 if !ok {
325                         return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
326                 }
327                 if typ != "null" {
328                         alias.Type = typ
329                 }
330         }
331
332         if lengthNode := aliasNode.At(aliasesLength); lengthNode.GetType() == jsongo.TypeValue {
333                 length, ok := lengthNode.Get().(float64)
334                 if !ok {
335                         return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
336                 }
337                 alias.Length = int(length)
338         }
339
340         return &alias, nil
341 }
342
343 // parseMessage parses VPP binary API message object from JSON node
344 func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
345         if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
346                 return nil, errors.New("invalid JSON for message specified")
347         }
348
349         msgName, ok := msgNode.At(0).Get().(string)
350         if !ok {
351                 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
352         }
353         msgCRC, ok := msgNode.At(msgNode.Len() - 1).At("crc").Get().(string)
354         if !ok {
355
356                 return nil, fmt.Errorf("message crc invalid or missing")
357         }
358
359         msg := Message{
360                 Name: msgName,
361                 CRC:  msgCRC,
362         }
363
364         // loop through message fields, skip first (name) and last (crc)
365         for j := 1; j < msgNode.Len()-1; j++ {
366                 if msgNode.At(j).GetType() == jsongo.TypeArray {
367                         fieldNode := msgNode.At(j)
368
369                         field, err := parseField(ctx, fieldNode)
370                         if err != nil {
371                                 return nil, err
372                         }
373
374                         msg.Fields = append(msg.Fields, *field)
375                 }
376         }
377
378         return &msg, nil
379 }
380
381 // parseField parses VPP binary API object field from JSON node
382 func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
383         if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
384                 return nil, errors.New("invalid JSON for field specified")
385         }
386
387         fieldType, ok := field.At(0).Get().(string)
388         if !ok {
389                 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
390         }
391         fieldName, ok := field.At(1).Get().(string)
392         if !ok {
393                 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
394         }
395         var fieldLength float64
396         if field.Len() >= 3 {
397                 fieldLength, ok = field.At(2).Get().(float64)
398                 if !ok {
399                         return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
400                 }
401         }
402         var fieldLengthFrom string
403         if field.Len() >= 4 {
404                 fieldLengthFrom, ok = field.At(3).Get().(string)
405                 if !ok {
406                         return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
407                 }
408         }
409
410         return &Field{
411                 Name:     fieldName,
412                 Type:     fieldType,
413                 Length:   int(fieldLength),
414                 SizeFrom: fieldLengthFrom,
415         }, nil
416 }
417
418 // parseService parses VPP binary API service object from JSON node
419 func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
420         if svcNode.Len() == 0 || svcNode.At("reply").GetType() != jsongo.TypeValue {
421                 return nil, errors.New("invalid JSON for service specified")
422         }
423
424         svc := Service{
425                 Name:        ctx.moduleName + "." + svcName,
426                 RequestType: svcName,
427         }
428
429         if replyNode := svcNode.At("reply"); replyNode.GetType() == jsongo.TypeValue {
430                 reply, ok := replyNode.Get().(string)
431                 if !ok {
432                         return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
433                 }
434                 if reply != "null" {
435                         svc.ReplyType = reply
436                 }
437         }
438
439         // stream service (dumps)
440         if streamNode := svcNode.At("stream"); streamNode.GetType() == jsongo.TypeValue {
441                 var ok bool
442                 svc.Stream, ok = streamNode.Get().(bool)
443                 if !ok {
444                         return nil, fmt.Errorf("service stream is %T, not a string", streamNode.Get())
445                 }
446         }
447
448         // events service (event subscription)
449         if eventsNode := svcNode.At("events"); eventsNode.GetType() == jsongo.TypeArray {
450                 for j := 0; j < eventsNode.Len(); j++ {
451                         event := eventsNode.At(j).Get().(string)
452                         svc.Events = append(svc.Events, event)
453                 }
454         }
455
456         // validate service
457         if svc.IsEventService() {
458                 if !strings.HasPrefix(svc.RequestType, "want_") {
459                         log.Warnf("Unusual EVENTS SERVICE: %+v\n"+
460                                 "- events service %q does not have 'want_' prefix in request.",
461                                 svc, svc.Name)
462                 }
463         } else if svc.IsDumpService() {
464                 if !strings.HasSuffix(svc.RequestType, "_dump") ||
465                         !strings.HasSuffix(svc.ReplyType, "_details") {
466                         log.Warnf("Unusual STREAM SERVICE: %+v\n"+
467                                 "- stream service %q does not have '_dump' suffix in request or reply does not have '_details' suffix.",
468                                 svc, svc.Name)
469                 }
470         } else if svc.IsRequestService() {
471                 if !strings.HasSuffix(svc.ReplyType, "_reply") {
472                         log.Warnf("Unusual REQUEST SERVICE: %+v\n"+
473                                 "- service %q does not have '_reply' suffix in reply.",
474                                 svc, svc.Name)
475                 }
476         }
477
478         return &svc, nil
479 }
480
481 // toApiType returns name that is used as type reference in VPP binary API
482 func toApiType(name string) string {
483         return fmt.Sprintf("vl_api_%s_t", name)
484 }
485
486 // convertToGoType translates the VPP binary API type into Go type
487 func convertToGoType(ctx *context, binapiType string) (typ string) {
488         if t, ok := binapiTypes[binapiType]; ok {
489                 // basic types
490                 typ = t
491         } else if r, ok := ctx.packageData.RefMap[binapiType]; ok {
492                 // specific types (enums/types/unions)
493                 typ = camelCaseName(r)
494         } else {
495                 switch binapiType {
496                 case "bool", "string":
497                         typ = binapiType
498                 default:
499                         // fallback type
500                         log.Warnf("found unknown VPP binary API type %q, using byte", binapiType)
501                         typ = "byte"
502                 }
503         }
504         return typ
505 }
506
507 func getSizeOfType(typ *Type) (size int) {
508         for _, field := range typ.Fields {
509                 size += getSizeOfBinapiTypeLength(field.Type, field.Length)
510         }
511         return size
512 }
513
514 func getSizeOfBinapiTypeLength(typ string, length int) (size int) {
515         if n := getBinapiTypeSize(typ); n > 0 {
516                 if length > 0 {
517                         return n * length
518                 } else {
519                         return n
520                 }
521         }
522         return
523 }
524
525 func getTypeByRef(ctx *context, ref string) *Type {
526         for _, typ := range ctx.packageData.Types {
527                 if ref == toApiType(typ.Name) {
528                         return &typ
529                 }
530         }
531         return nil
532 }
533
534 func getAliasByRef(ctx *context, ref string) *Alias {
535         for _, alias := range ctx.packageData.Aliases {
536                 if ref == toApiType(alias.Name) {
537                         return &alias
538                 }
539         }
540         return nil
541 }
542
543 func getUnionSize(ctx *context, union *Union) (maxSize int) {
544         for _, field := range union.Fields {
545                 typ := getTypeByRef(ctx, field.Type)
546                 if typ != nil {
547                         if size := getSizeOfType(typ); size > maxSize {
548                                 maxSize = size
549                         }
550                         continue
551                 }
552                 alias := getAliasByRef(ctx, field.Type)
553                 if alias != nil {
554                         if size := getSizeOfBinapiTypeLength(alias.Type, alias.Length); size > maxSize {
555                                 maxSize = size
556                         }
557                         continue
558                 } else {
559                         logf("no type or alias found for union %s field type %q", union.Name, field.Type)
560                         continue
561                 }
562         }
563         return
564 }