1 // Copyright (c) 2020 Cisco and/or its affiliates.
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:
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
25 "github.com/bennyscetbun/jsongo"
28 var debug = strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser")
30 func logf(f string, v ...interface{}) {
38 fileAPIVersion = "vl_api_version"
39 fileOptions = "options"
41 fileMessages = "messages"
44 fileEnumflags = "enumflags"
45 fileAliases = "aliases"
46 fileServices = "services"
47 fileImports = "imports"
50 messageOptions = "options"
52 aliasLength = "length"
55 serviceReply = "reply"
56 serviceStream = "stream"
57 serviceStreamMsg = "stream_msg"
58 serviceEvents = "events"
61 func parseJSON(data []byte) (module *File, err error) {
63 jsonRoot := new(jsongo.Node)
64 if err := json.Unmarshal(data, jsonRoot); err != nil {
65 return nil, fmt.Errorf("unmarshalling JSON failed: %v", err)
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)
78 crc := jsonRoot.At(fileAPIVersion)
79 if crc.GetType() == jsongo.TypeValue {
80 module.CRC = crc.MustGetString()
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
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)
105 uniq[imp] = struct{}{}
106 module.Imports = append(module.Imports, imp)
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)
116 known[name] = struct{}{}
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))
128 if exists(enum.Name) {
131 module.EnumTypes = append(module.EnumTypes, *enum)
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))
142 if exists(enumflag.Name) {
145 module.EnumflagTypes = append(module.EnumflagTypes, *enumflag)
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))
158 if exists(alias.Name) {
161 module.AliasTypes = append(module.AliasTypes, *alias)
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))
173 if exists(structyp.Name) {
176 module.StructTypes = append(module.StructTypes, *structyp)
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))
187 if exists(union.Name) {
190 module.UnionTypes = append(module.UnionTypes, *union)
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))
202 module.Messages[i] = *msg
207 servicesNode := jsonRoot.Map(fileServices)
208 if servicesNode.GetType() == jsongo.TypeMap {
209 module.Service = &Service{
210 RPCs: make([]RPC, servicesNode.Len()),
212 for i, key := range servicesNode.GetKeys() {
213 rpcName := key.(string)
214 svc, err := parseServiceRPC(rpcName, servicesNode.At(key))
218 module.Service.RPCs[i] = *svc
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")
231 enumName, ok := enumNode.At(0).Get().(string)
233 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
235 enumType, ok := enumNode.At(enumNode.Len() - 1).At(enumType).Get().(string)
237 return nil, fmt.Errorf("enum type invalid or missing")
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)
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")
254 entryName, ok := entry.At(0).Get().(string)
256 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
258 entryVal := entry.At(1).Get().(float64)
260 enum.Entries = append(enum.Entries, EnumEntry{
262 Value: uint32(entryVal),
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")
276 unionName, ok := unionNode.At(0).Get().(string)
278 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
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)
290 field, err := parseField(fieldNode)
295 union.Fields = append(union.Fields, *field)
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")
308 typeName, ok := typeNode.At(0).Get().(string)
310 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
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)
322 field, err := parseField(fieldNode)
327 typ.Fields = append(typ.Fields, *field)
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")
344 if typeNode := aliasNode.At(aliasType); typeNode.GetType() == jsongo.TypeValue {
345 typ, ok := typeNode.Get().(string)
347 return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
354 if lengthNode := aliasNode.At(aliasLength); lengthNode.GetType() == jsongo.TypeValue {
355 length, ok := lengthNode.Get().(float64)
357 return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
359 alias.Length = int(length)
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")
371 msgName, ok := msgNode.At(0).Get().(string)
373 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
375 msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageCrc).Get().(string)
377 return nil, fmt.Errorf("message crc invalid or missing")
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")
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
393 logf("invalid message option value, expected string")
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)
410 field, err := parseField(fieldNode)
415 msg.Fields = append(msg.Fields, *field)
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")
428 fieldType, ok := field.At(0).Get().(string)
430 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
432 fieldName, ok := field.At(1).Get().(string)
434 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
442 if field.Len() >= 3 {
443 switch field.At(2).GetType() {
444 case jsongo.TypeValue:
445 fieldLength, ok := field.At(2).Get().(float64)
447 return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get())
449 f.Length = int(fieldLength)
453 fieldMeta := field.At(2)
454 if fieldMeta.Len() == 0 {
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
465 return nil, errors.New("invalid JSON for field specified")
468 if field.Len() >= 4 {
469 fieldLengthFrom, ok := field.At(3).Get().(string)
471 return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
473 f.SizeFrom = fieldLengthFrom
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")
489 if replyNode := rpcNode.At(serviceReply); replyNode.GetType() == jsongo.TypeValue {
490 reply, ok := replyNode.Get().(string)
492 return nil, fmt.Errorf("service RPC reply is %T, not a string", replyNode.Get())
498 if streamNode := rpcNode.At(serviceStream); streamNode.GetType() == jsongo.TypeValue {
500 rpc.Stream, ok = streamNode.Get().(bool)
502 return nil, fmt.Errorf("service RPC stream is %T, not a boolean", streamNode.Get())
507 if streamMsgNode := rpcNode.At(serviceStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue {
509 rpc.StreamMsg, ok = streamMsgNode.Get().(string)
511 return nil, fmt.Errorf("service RPC stream msg is %T, not a string", streamMsgNode.Get())
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)