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