45b57960c021edebc5f27333a26aed7538aba551
[govpp.git] / binapigen / vppapi / parse_json.go
1 //  Copyright (c) 2020 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 vppapi
16
17 import (
18         "encoding/json"
19         "errors"
20         "fmt"
21         "os"
22         "strings"
23
24         "github.com/bennyscetbun/jsongo"
25         "github.com/sirupsen/logrus"
26 )
27
28 var Logger *logrus.Logger
29
30 func init() {
31         if strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser") {
32                 Logger = logrus.StandardLogger()
33         }
34 }
35
36 func logf(f string, v ...interface{}) {
37         if Logger != nil {
38                 Logger.Debugf(f, v...)
39         }
40 }
41
42 const (
43         // file
44         objAPIVersion = "vl_api_version"
45         objTypes      = "types"
46         objMessages   = "messages"
47         objUnions     = "unions"
48         objEnums      = "enums"
49         objServices   = "services"
50         objAliases    = "aliases"
51         objOptions    = "options"
52         objImports    = "imports"
53
54         // message
55         messageFieldCrc = "crc"
56
57         // alias
58         aliasFieldLength = "length"
59         aliasFieldType   = "type"
60
61         // service
62         serviceFieldReply     = "reply"
63         serviceFieldStream    = "stream"
64         serviceFieldStreamMsg = "stream_msg"
65         serviceFieldEvents    = "events"
66 )
67
68 const (
69         // file
70         fileOptionVersion = "version"
71
72         // field
73         fieldOptionLimit   = "limit"
74         fieldOptionDefault = "default"
75
76         // service
77         serviceReplyNull = "null"
78 )
79
80 func parseJSON(data []byte) (module *File, err error) {
81         defer func() {
82                 if e := recover(); e != nil {
83                         err = fmt.Errorf("recovered panic: %v", e)
84                 }
85         }()
86
87         // parse JSON data into objects
88         jsonRoot := new(jsongo.Node)
89         if err := json.Unmarshal(data, jsonRoot); err != nil {
90                 return nil, fmt.Errorf("unmarshalling JSON failed: %v", err)
91         }
92
93         logf("file contents:")
94         for _, key := range jsonRoot.GetKeys() {
95                 if jsonRoot.At(key).Len() > 0 {
96                         logf("  - %2d %s", jsonRoot.At(key).Len(), key)
97                 }
98         }
99
100         module = new(File)
101
102         // parse CRC
103         if crc := jsonRoot.At(objAPIVersion); crc.GetType() == jsongo.TypeValue {
104                 module.CRC = crc.Get().(string)
105         }
106
107         // parse options
108         opt := jsonRoot.Map(objOptions)
109         if opt.GetType() == jsongo.TypeMap {
110                 module.Options = make(map[string]string, 0)
111                 for _, key := range opt.GetKeys() {
112                         optionsNode := opt.At(key)
113                         optionKey := key.(string)
114                         optionValue := optionsNode.Get().(string)
115                         module.Options[optionKey] = optionValue
116                 }
117         }
118
119         // parse imports
120         imports := jsonRoot.Map(objImports)
121         module.Imports = make([]string, 0)
122         imported := make(map[string]struct{})
123         for i := 0; i < imports.Len(); i++ {
124                 importNode := imports.At(i)
125                 imp, err := parseImport(importNode)
126                 if err != nil {
127                         return nil, err
128                 }
129                 if _, ok := imported[*imp]; ok {
130                         logf("duplicate import found: %v", *imp)
131                         continue
132                 }
133                 imported[*imp] = struct{}{}
134                 module.Imports = append(module.Imports, *imp)
135         }
136
137         // avoid duplicate objects
138         known := make(map[string]struct{})
139         exists := func(name string) bool {
140                 if _, ok := known[name]; ok {
141                         logf("duplicate object found: %v", name)
142                         return true
143                 }
144                 known[name] = struct{}{}
145                 return false
146         }
147
148         // parse enum types
149         enumsNode := jsonRoot.Map(objEnums)
150         module.EnumTypes = make([]EnumType, 0)
151         for i := 0; i < enumsNode.Len(); i++ {
152                 enumNode := enumsNode.At(i)
153                 enum, err := parseEnum(enumNode)
154                 if err != nil {
155                         return nil, err
156                 }
157                 if exists(enum.Name) {
158                         continue
159                 }
160                 module.EnumTypes = append(module.EnumTypes, *enum)
161         }
162
163         // parse alias types
164         aliasesNode := jsonRoot.Map(objAliases)
165         if aliasesNode.GetType() == jsongo.TypeMap {
166                 module.AliasTypes = make([]AliasType, 0)
167                 for _, key := range aliasesNode.GetKeys() {
168                         aliasNode := aliasesNode.At(key)
169                         aliasName := key.(string)
170                         alias, err := parseAlias(aliasName, aliasNode)
171                         if err != nil {
172                                 return nil, err
173                         }
174                         if exists(alias.Name) {
175                                 continue
176                         }
177                         module.AliasTypes = append(module.AliasTypes, *alias)
178                 }
179         }
180
181         // parse struct types
182         typesNode := jsonRoot.Map(objTypes)
183         module.StructTypes = make([]StructType, 0)
184         for i := 0; i < typesNode.Len(); i++ {
185                 typNode := typesNode.At(i)
186                 structyp, err := parseStruct(typNode)
187                 if err != nil {
188                         return nil, err
189                 }
190                 if exists(structyp.Name) {
191                         continue
192                 }
193                 module.StructTypes = append(module.StructTypes, *structyp)
194         }
195
196         // parse union types
197         unionsNode := jsonRoot.Map(objUnions)
198         module.UnionTypes = make([]UnionType, 0)
199         for i := 0; i < unionsNode.Len(); i++ {
200                 unionNode := unionsNode.At(i)
201                 union, err := parseUnion(unionNode)
202                 if err != nil {
203                         return nil, err
204                 }
205                 if exists(union.Name) {
206                         continue
207                 }
208                 module.UnionTypes = append(module.UnionTypes, *union)
209         }
210
211         // parse messages
212         messagesNode := jsonRoot.Map(objMessages)
213         if messagesNode.GetType() == jsongo.TypeArray {
214                 module.Messages = make([]Message, messagesNode.Len())
215                 for i := 0; i < messagesNode.Len(); i++ {
216                         msgNode := messagesNode.At(i)
217                         msg, err := parseMessage(msgNode)
218                         if err != nil {
219                                 return nil, err
220                         }
221                         module.Messages[i] = *msg
222                 }
223         }
224
225         // parse services
226         servicesNode := jsonRoot.Map(objServices)
227         if servicesNode.GetType() == jsongo.TypeMap {
228                 module.Service = &Service{
229                         RPCs: make([]RPC, servicesNode.Len()),
230                 }
231                 for i, key := range servicesNode.GetKeys() {
232                         rpcNode := servicesNode.At(key)
233                         rpcName := key.(string)
234                         svc, err := parseServiceRPC(rpcName, rpcNode)
235                         if err != nil {
236                                 return nil, err
237                         }
238                         module.Service.RPCs[i] = *svc
239                 }
240         }
241
242         return module, nil
243 }
244
245 // parseImport parses VPP binary API import from JSON node
246 func parseImport(importNode *jsongo.Node) (*string, error) {
247         if importNode.GetType() != jsongo.TypeValue {
248                 return nil, errors.New("invalid JSON for import specified")
249         }
250
251         importName, ok := importNode.Get().(string)
252         if !ok {
253                 return nil, fmt.Errorf("import name is %T, not a string", importNode.Get())
254         }
255
256         return &importName, nil
257 }
258
259 // parseEnum parses VPP binary API enum object from JSON node
260 func parseEnum(enumNode *jsongo.Node) (*EnumType, error) {
261         if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
262                 return nil, errors.New("invalid JSON for enum specified")
263         }
264
265         enumName, ok := enumNode.At(0).Get().(string)
266         if !ok {
267                 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
268         }
269         enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
270         if !ok {
271                 return nil, fmt.Errorf("enum type invalid or missing")
272         }
273
274         enum := EnumType{
275                 Name: enumName,
276                 Type: enumType,
277         }
278
279         // loop through enum entries, skip first (name) and last (enumtype)
280         for j := 1; j < enumNode.Len()-1; j++ {
281                 if enumNode.At(j).GetType() == jsongo.TypeArray {
282                         entry := enumNode.At(j)
283
284                         if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue {
285                                 return nil, errors.New("invalid JSON for enum entry specified")
286                         }
287
288                         entryName, ok := entry.At(0).Get().(string)
289                         if !ok {
290                                 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
291                         }
292                         entryVal := entry.At(1).Get().(float64)
293
294                         enum.Entries = append(enum.Entries, EnumEntry{
295                                 Name:  entryName,
296                                 Value: uint32(entryVal),
297                         })
298                 }
299         }
300
301         return &enum, nil
302 }
303
304 // parseUnion parses VPP binary API union object from JSON node
305 func parseUnion(unionNode *jsongo.Node) (*UnionType, error) {
306         if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
307                 return nil, errors.New("invalid JSON for union specified")
308         }
309
310         unionName, ok := unionNode.At(0).Get().(string)
311         if !ok {
312                 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
313         }
314
315         union := UnionType{
316                 Name: unionName,
317         }
318
319         // loop through union fields, skip first (name)
320         for j := 1; j < unionNode.Len(); j++ {
321                 if unionNode.At(j).GetType() == jsongo.TypeArray {
322                         fieldNode := unionNode.At(j)
323
324                         field, err := parseField(fieldNode)
325                         if err != nil {
326                                 return nil, err
327                         }
328
329                         union.Fields = append(union.Fields, *field)
330                 }
331         }
332
333         return &union, nil
334 }
335
336 // parseStruct parses VPP binary API type object from JSON node
337 func parseStruct(typeNode *jsongo.Node) (*StructType, error) {
338         if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
339                 return nil, errors.New("invalid JSON for type specified")
340         }
341
342         typeName, ok := typeNode.At(0).Get().(string)
343         if !ok {
344                 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
345         }
346
347         typ := StructType{
348                 Name: typeName,
349         }
350
351         // loop through type fields, skip first (name)
352         for j := 1; j < typeNode.Len(); j++ {
353                 if typeNode.At(j).GetType() == jsongo.TypeArray {
354                         fieldNode := typeNode.At(j)
355
356                         field, err := parseField(fieldNode)
357                         if err != nil {
358                                 return nil, err
359                         }
360
361                         typ.Fields = append(typ.Fields, *field)
362                 }
363         }
364
365         return &typ, nil
366 }
367
368 // parseAlias parses VPP binary API alias object from JSON node
369 func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
370         if aliasNode.Len() == 0 || aliasNode.At(aliasFieldType).GetType() != jsongo.TypeValue {
371                 return nil, errors.New("invalid JSON for alias specified")
372         }
373
374         alias := AliasType{
375                 Name: aliasName,
376         }
377
378         if typeNode := aliasNode.At(aliasFieldType); typeNode.GetType() == jsongo.TypeValue {
379                 typ, ok := typeNode.Get().(string)
380                 if !ok {
381                         return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
382                 }
383                 if typ != "null" {
384                         alias.Type = typ
385                 }
386         }
387
388         if lengthNode := aliasNode.At(aliasFieldLength); lengthNode.GetType() == jsongo.TypeValue {
389                 length, ok := lengthNode.Get().(float64)
390                 if !ok {
391                         return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
392                 }
393                 alias.Length = int(length)
394         }
395
396         return &alias, nil
397 }
398
399 // parseMessage parses VPP binary API message object from JSON node
400 func parseMessage(msgNode *jsongo.Node) (*Message, error) {
401         if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
402                 return nil, errors.New("invalid JSON for message specified")
403         }
404
405         msgName, ok := msgNode.At(0).Get().(string)
406         if !ok {
407                 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
408         }
409         msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageFieldCrc).Get().(string)
410         if !ok {
411
412                 return nil, fmt.Errorf("message crc invalid or missing")
413         }
414
415         msg := Message{
416                 Name: msgName,
417                 CRC:  msgCRC,
418         }
419
420         // loop through message fields, skip first (name) and last (crc)
421         for j := 1; j < msgNode.Len()-1; j++ {
422                 if msgNode.At(j).GetType() == jsongo.TypeArray {
423                         fieldNode := msgNode.At(j)
424
425                         field, err := parseField(fieldNode)
426                         if err != nil {
427                                 return nil, err
428                         }
429
430                         msg.Fields = append(msg.Fields, *field)
431                 }
432         }
433
434         return &msg, nil
435 }
436
437 // parseField parses VPP binary API object field from JSON node
438 func parseField(field *jsongo.Node) (*Field, error) {
439         if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
440                 return nil, errors.New("invalid JSON for field specified")
441         }
442
443         fieldType, ok := field.At(0).Get().(string)
444         if !ok {
445                 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
446         }
447         fieldName, ok := field.At(1).Get().(string)
448         if !ok {
449                 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
450         }
451
452         f := &Field{
453                 Name: fieldName,
454                 Type: fieldType,
455         }
456
457         if field.Len() >= 3 {
458                 switch field.At(2).GetType() {
459                 case jsongo.TypeValue:
460                         fieldLength, ok := field.At(2).Get().(float64)
461                         if !ok {
462                                 return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
463                         }
464                         f.Length = int(fieldLength)
465                         f.Array = true
466
467                 case jsongo.TypeMap:
468                         fieldMeta := field.At(2)
469
470                         for _, key := range fieldMeta.GetKeys() {
471                                 metaNode := fieldMeta.At(key)
472                                 metaName := key.(string)
473                                 metaValue := metaNode.Get()
474
475                                 switch metaName {
476                                 case fieldOptionLimit:
477                                         metaValue = int(metaNode.Get().(float64))
478                                 case fieldOptionDefault:
479                                         metaValue = metaNode.Get()
480                                 default:
481                                         logrus.Warnf("unknown meta info (%s=%v) for field (%s)", metaName, metaValue, fieldName)
482                                 }
483
484                                 if f.Meta == nil {
485                                         f.Meta = map[string]interface{}{}
486                                 }
487                                 f.Meta[metaName] = metaValue
488                         }
489                 default:
490                         return nil, errors.New("invalid JSON for field specified")
491                 }
492         }
493         if field.Len() >= 4 {
494                 fieldLengthFrom, ok := field.At(3).Get().(string)
495                 if !ok {
496                         return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
497                 }
498                 f.SizeFrom = fieldLengthFrom
499         }
500
501         return f, nil
502 }
503
504 // parseServiceRPC parses VPP binary API service object from JSON node
505 func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) {
506         if rpcNode.Len() == 0 || rpcNode.At(serviceFieldReply).GetType() != jsongo.TypeValue {
507                 return nil, errors.New("invalid JSON for service RPC specified")
508         }
509
510         rpc := RPC{
511                 Name:       rpcName,
512                 RequestMsg: rpcName,
513         }
514
515         if replyNode := rpcNode.At(serviceFieldReply); replyNode.GetType() == jsongo.TypeValue {
516                 reply, ok := replyNode.Get().(string)
517                 if !ok {
518                         return nil, fmt.Errorf("service RPC reply is %T, not a string", replyNode.Get())
519                 }
520                 if reply != serviceReplyNull {
521                         rpc.ReplyMsg = reply
522                 }
523         }
524
525         // is stream (dump)
526         if streamNode := rpcNode.At(serviceFieldStream); streamNode.GetType() == jsongo.TypeValue {
527                 var ok bool
528                 rpc.Stream, ok = streamNode.Get().(bool)
529                 if !ok {
530                         return nil, fmt.Errorf("service RPC stream is %T, not a boolean", streamNode.Get())
531                 }
532         }
533
534         // stream message
535         if streamMsgNode := rpcNode.At(serviceFieldStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue {
536                 var ok bool
537                 rpc.StreamMsg, ok = streamMsgNode.Get().(string)
538                 if !ok {
539                         return nil, fmt.Errorf("service RPC stream msg is %T, not a string", streamMsgNode.Get())
540                 }
541         }
542
543         // events service (event subscription)
544         if eventsNode := rpcNode.At(serviceFieldEvents); eventsNode.GetType() == jsongo.TypeArray {
545                 for j := 0; j < eventsNode.Len(); j++ {
546                         event := eventsNode.At(j).Get().(string)
547                         rpc.Events = append(rpc.Events, event)
548                 }
549         }
550
551         return &rpc, nil
552 }