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