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