07abebdb2eca98690ee26903af4983425cdfe15b
[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         // sort enums
58         sort.SliceStable(pkg.Enums, func(i, j int) bool {
59                 return pkg.Enums[i].Name < pkg.Enums[j].Name
60         })
61
62         // parse aliases
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)
68
69                         alias, err := parseAlias(ctx, key.(string), aliasNode)
70                         if err != nil {
71                                 return nil, err
72                         }
73                         pkg.Aliases[i] = *alias
74                         pkg.RefMap[toApiType(alias.Name)] = alias.Name
75                 }
76         }
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
80         })
81
82         // parse types
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)
87
88                 typ, err := parseType(ctx, typNode)
89                 if err != nil {
90                         return nil, err
91                 }
92                 pkg.Types[i] = *typ
93                 pkg.RefMap[toApiType(typ.Name)] = typ.Name
94         }
95         // sort types
96         sort.SliceStable(pkg.Types, func(i, j int) bool {
97                 return pkg.Types[i].Name < pkg.Types[j].Name
98         })
99
100         // parse unions
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)
105
106                 union, err := parseUnion(ctx, unionNode)
107                 if err != nil {
108                         return nil, err
109                 }
110                 pkg.Unions[i] = *union
111                 pkg.RefMap[toApiType(union.Name)] = union.Name
112         }
113         // sort unions
114         sort.SliceStable(pkg.Unions, func(i, j int) bool {
115                 return pkg.Unions[i].Name < pkg.Unions[j].Name
116         })
117
118         // parse messages
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)
123
124                 msg, err := parseMessage(ctx, msgNode)
125                 if err != nil {
126                         return nil, err
127                 }
128                 pkg.Messages[i] = *msg
129         }
130         // sort messages
131         sort.SliceStable(pkg.Messages, func(i, j int) bool {
132                 return pkg.Messages[i].Name < pkg.Messages[j].Name
133         })
134
135         // parse services
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)
141
142                         svc, err := parseService(ctx, key.(string), svcNode)
143                         if err != nil {
144                                 return nil, err
145                         }
146                         pkg.Services[i] = *svc
147                 }
148         }
149         // sort services
150         sort.Slice(pkg.Services, func(i, j int) bool {
151                 // dumps first
152                 if pkg.Services[i].Stream != pkg.Services[j].Stream {
153                         return pkg.Services[i].Stream
154                 }
155                 return pkg.Services[i].RequestType < pkg.Services[j].RequestType
156         })
157
158         printPackage(&pkg)
159
160         return &pkg, nil
161 }
162
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)
169                 }
170         }
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)
175                 }
176         }
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))
181                 }
182         }
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))
187                 }
188         }
189         if len(pkg.Services) > 0 {
190                 logf("loaded %d services:", len(pkg.Services))
191                 for _, svc := range pkg.Services {
192                         var info string
193                         if svc.Stream {
194                                 info = "(STREAM)"
195                         } else if len(svc.Events) > 0 {
196                                 info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
197                         }
198                         logf(" - service: %q -> %q %s", svc.RequestType, svc.ReplyType, info)
199                 }
200         }
201 }
202
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")
207         }
208
209         enumName, ok := enumNode.At(0).Get().(string)
210         if !ok {
211                 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
212         }
213         enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
214         if !ok {
215                 return nil, fmt.Errorf("enum type invalid or missing")
216         }
217
218         enum := Enum{
219                 Name: enumName,
220                 Type: enumType,
221         }
222
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)
227
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")
230                         }
231
232                         entryName, ok := entry.At(0).Get().(string)
233                         if !ok {
234                                 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
235                         }
236                         entryVal := entry.At(1).Get()
237
238                         enum.Entries = append(enum.Entries, EnumEntry{
239                                 Name:  entryName,
240                                 Value: entryVal,
241                         })
242                 }
243         }
244
245         return &enum, nil
246 }
247
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")
252         }
253
254         unionName, ok := unionNode.At(0).Get().(string)
255         if !ok {
256                 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
257         }
258         unionCRC, ok := unionNode.At(unionNode.Len() - 1).At("crc").Get().(string)
259         if !ok {
260                 return nil, fmt.Errorf("union crc invalid or missing")
261         }
262
263         union := Union{
264                 Name: unionName,
265                 CRC:  unionCRC,
266         }
267
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)
272
273                         field, err := parseField(ctx, fieldNode)
274                         if err != nil {
275                                 return nil, err
276                         }
277
278                         union.Fields = append(union.Fields, *field)
279                 }
280         }
281
282         return &union, nil
283 }
284
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")
289         }
290
291         typeName, ok := typeNode.At(0).Get().(string)
292         if !ok {
293                 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
294         }
295         typeCRC, ok := typeNode.At(typeNode.Len() - 1).At("crc").Get().(string)
296         if !ok {
297                 return nil, fmt.Errorf("type crc invalid or missing")
298         }
299
300         typ := Type{
301                 Name: typeName,
302                 CRC:  typeCRC,
303         }
304
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)
309
310                         field, err := parseField(ctx, fieldNode)
311                         if err != nil {
312                                 return nil, err
313                         }
314
315                         typ.Fields = append(typ.Fields, *field)
316                 }
317         }
318
319         return &typ, nil
320 }
321
322 const (
323         aliasesLength = "length"
324         aliasesType   = "type"
325 )
326
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")
331         }
332
333         alias := Alias{
334                 Name: aliasName,
335         }
336
337         if typeNode := aliasNode.At(aliasesType); typeNode.GetType() == jsongo.TypeValue {
338                 typ, ok := typeNode.Get().(string)
339                 if !ok {
340                         return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
341                 }
342                 if typ != "null" {
343                         alias.Type = typ
344                 }
345         }
346
347         if lengthNode := aliasNode.At(aliasesLength); lengthNode.GetType() == jsongo.TypeValue {
348                 length, ok := lengthNode.Get().(float64)
349                 if !ok {
350                         return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
351                 }
352                 alias.Length = int(length)
353         }
354
355         return &alias, nil
356 }
357
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")
362         }
363
364         msgName, ok := msgNode.At(0).Get().(string)
365         if !ok {
366                 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
367         }
368         msgCRC, ok := msgNode.At(msgNode.Len() - 1).At("crc").Get().(string)
369         if !ok {
370
371                 return nil, fmt.Errorf("message crc invalid or missing")
372         }
373
374         msg := Message{
375                 Name: msgName,
376                 CRC:  msgCRC,
377         }
378
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)
383
384                         field, err := parseField(ctx, fieldNode)
385                         if err != nil {
386                                 return nil, err
387                         }
388
389                         msg.Fields = append(msg.Fields, *field)
390                 }
391         }
392
393         return &msg, nil
394 }
395
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")
400         }
401
402         fieldType, ok := field.At(0).Get().(string)
403         if !ok {
404                 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
405         }
406         fieldName, ok := field.At(1).Get().(string)
407         if !ok {
408                 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
409         }
410         var fieldLength float64
411         if field.Len() >= 3 {
412                 fieldLength, ok = field.At(2).Get().(float64)
413                 if !ok {
414                         return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
415                 }
416         }
417         var fieldLengthFrom string
418         if field.Len() >= 4 {
419                 fieldLengthFrom, ok = field.At(3).Get().(string)
420                 if !ok {
421                         return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
422                 }
423         }
424
425         return &Field{
426                 Name:     fieldName,
427                 Type:     fieldType,
428                 Length:   int(fieldLength),
429                 SizeFrom: fieldLengthFrom,
430         }, nil
431 }
432
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")
437         }
438
439         svc := Service{
440                 Name:        ctx.moduleName + "." + svcName,
441                 RequestType: svcName,
442         }
443
444         if replyNode := svcNode.At("reply"); replyNode.GetType() == jsongo.TypeValue {
445                 reply, ok := replyNode.Get().(string)
446                 if !ok {
447                         return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
448                 }
449                 if reply != "null" {
450                         svc.ReplyType = reply
451                 }
452         }
453
454         // stream service (dumps)
455         if streamNode := svcNode.At("stream"); streamNode.GetType() == jsongo.TypeValue {
456                 var ok bool
457                 svc.Stream, ok = streamNode.Get().(bool)
458                 if !ok {
459                         return nil, fmt.Errorf("service stream is %T, not a string", streamNode.Get())
460                 }
461         }
462
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)
468                 }
469         }
470
471         // validate service
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.",
476                                 svc, svc.Name)
477                 }
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.",
483                                 svc, svc.Name)
484                 }
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.",
489                                 svc, svc.Name)
490                 }
491         }
492
493         return &svc, nil
494 }
495
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)
499 }
500
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 {
504                 // basic types
505                 typ = t
506         } else if r, ok := ctx.packageData.RefMap[binapiType]; ok {
507                 // specific types (enums/types/unions)
508                 typ = camelCaseName(r)
509         } else {
510                 switch binapiType {
511                 case "bool", "string":
512                         typ = binapiType
513                 default:
514                         // fallback type
515                         log.Warnf("found unknown VPP binary API type %q, using byte", binapiType)
516                         typ = "byte"
517                 }
518         }
519         return typ
520 }
521
522 func getSizeOfType(typ *Type) (size int) {
523         for _, field := range typ.Fields {
524                 size += getSizeOfBinapiTypeLength(field.Type, field.Length)
525         }
526         return size
527 }
528
529 func getSizeOfBinapiTypeLength(typ string, length int) (size int) {
530         if n := getBinapiTypeSize(typ); n > 0 {
531                 if length > 0 {
532                         return n * length
533                 } else {
534                         return n
535                 }
536         }
537         return
538 }
539
540 func getTypeByRef(ctx *context, ref string) *Type {
541         for _, typ := range ctx.packageData.Types {
542                 if ref == toApiType(typ.Name) {
543                         return &typ
544                 }
545         }
546         return nil
547 }
548
549 func getAliasByRef(ctx *context, ref string) *Alias {
550         for _, alias := range ctx.packageData.Aliases {
551                 if ref == toApiType(alias.Name) {
552                         return &alias
553                 }
554         }
555         return nil
556 }
557
558 func getUnionSize(ctx *context, union *Union) (maxSize int) {
559         for _, field := range union.Fields {
560                 typ := getTypeByRef(ctx, field.Type)
561                 if typ != nil {
562                         if size := getSizeOfType(typ); size > maxSize {
563                                 maxSize = size
564                         }
565                         continue
566                 }
567                 alias := getAliasByRef(ctx, field.Type)
568                 if alias != nil {
569                         if size := getSizeOfBinapiTypeLength(alias.Type, alias.Length); size > maxSize {
570                                 maxSize = size
571                         }
572                         continue
573                 } else {
574                         logf("no type or alias found for union %s field type %q", union.Name, field.Type)
575                         continue
576                 }
577         }
578         return
579 }