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