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