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