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