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