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