diff options
author | Ondrej Fabry <ofabry@cisco.com> | 2020-07-17 10:36:28 +0200 |
---|---|---|
committer | Ondrej Fabry <ofabry@cisco.com> | 2020-07-17 11:43:41 +0200 |
commit | d1f24d37bd447b64e402298bb8eb2479681facf9 (patch) | |
tree | a3fc21ba730a91d8a402c7a5bf9c614e3677c4fc /binapigen/vppapi | |
parent | 1548c7e12531e3d055567d761c580a1c7ff0ac40 (diff) |
Improve binapi generator
- simplified Size/Marshal/Unmarshal methods
- replace struc in unions with custom marshal/unmarshal
- fix imports in generated files
- fix mock adapter
- generate rpc service using low-level stream API (dumps generate control ping or stream msg..)
- move examples/binapi to binapi and generate all API for latest release
- add binapigen.Plugin for developing custom generator plugins
- optionally generate HTTP handlers (REST API) for RPC services
- add govpp program for browsing VPP API
Change-Id: I092e9ed2b0c17972b3476463c3d4b14dd76ed42b
Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
Diffstat (limited to 'binapigen/vppapi')
-rw-r--r-- | binapigen/vppapi/api.go | 94 | ||||
-rw-r--r-- | binapigen/vppapi/api_schema.go | 89 | ||||
-rw-r--r-- | binapigen/vppapi/parse_json.go | 210 | ||||
-rw-r--r-- | binapigen/vppapi/util.go | 112 | ||||
-rw-r--r-- | binapigen/vppapi/vppapi.go (renamed from binapigen/vppapi/parser.go) | 49 | ||||
-rw-r--r-- | binapigen/vppapi/vppapi_test.go (renamed from binapigen/vppapi/parser_test.go) | 22 |
6 files changed, 314 insertions, 262 deletions
diff --git a/binapigen/vppapi/api.go b/binapigen/vppapi/api.go deleted file mode 100644 index 06d9046..0000000 --- a/binapigen/vppapi/api.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2020 Cisco and/or its affiliates. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vppapi - -type File struct { - Name string - Path string - - CRC string - Options map[string]string `json:",omitempty"` - - Imports []string `json:",omitempty"` - - AliasTypes []AliasType `json:",omitempty"` - EnumTypes []EnumType `json:",omitempty"` - StructTypes []StructType `json:",omitempty"` - UnionTypes []UnionType `json:",omitempty"` - Messages []Message `json:",omitempty"` - Service *Service `json:",omitempty"` -} - -func (x File) Version() string { - if x.Options != nil { - return x.Options[fileOptionVersion] - } - return "" -} - -type AliasType struct { - Name string - Type string - Length int `json:",omitempty"` -} - -type EnumType struct { - Name string - Type string - Entries []EnumEntry -} - -type EnumEntry struct { - Name string - Value uint32 -} - -type StructType struct { - Name string - Fields []Field -} - -type UnionType struct { - Name string - Fields []Field -} - -type Message struct { - Name string - Fields []Field - CRC string -} - -type Field struct { - Name string - Type string - Length int `json:",omitempty"` - Array bool `json:",omitempty"` - SizeFrom string `json:",omitempty"` - Meta map[string]interface{} `json:",omitempty"` -} - -type Service struct { - RPCs []RPC `json:",omitempty"` -} - -type RPC struct { - Name string - RequestMsg string - ReplyMsg string - Stream bool `json:",omitempty"` - StreamMsg string `json:",omitempty"` - Events []string `json:",omitempty"` -} diff --git a/binapigen/vppapi/api_schema.go b/binapigen/vppapi/api_schema.go new file mode 100644 index 0000000..7eceab3 --- /dev/null +++ b/binapigen/vppapi/api_schema.go @@ -0,0 +1,89 @@ +// Copyright (c) 2020 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package vppapi parses VPP API files without any additional processing. +package vppapi + +type ( + File struct { + Name string + Path string + CRC string + + Options map[string]string `json:",omitempty"` + Imports []string `json:",omitempty"` + + AliasTypes []AliasType `json:",omitempty"` + EnumTypes []EnumType `json:",omitempty"` + StructTypes []StructType `json:",omitempty"` + UnionTypes []UnionType `json:",omitempty"` + + Messages []Message `json:",omitempty"` + Service *Service `json:",omitempty"` + } + + AliasType struct { + Name string + Type string + Length int `json:",omitempty"` + } + + EnumType struct { + Name string + Type string + Entries []EnumEntry + } + + EnumEntry struct { + Name string + Value uint32 + } + + StructType struct { + Name string + Fields []Field + } + + UnionType struct { + Name string + Fields []Field + } + + Message struct { + Name string + Fields []Field + CRC string + } + + Field struct { + Name string + Type string + Length int `json:",omitempty"` + Array bool `json:",omitempty"` + SizeFrom string `json:",omitempty"` + Meta map[string]interface{} `json:",omitempty"` + } + + Service struct { + RPCs []RPC `json:",omitempty"` + } + + RPC struct { + Request string + Reply string + Stream bool `json:",omitempty"` + StreamMsg string `json:",omitempty"` + Events []string `json:",omitempty"` + } +) diff --git a/binapigen/vppapi/parse_json.go b/binapigen/vppapi/parse_json.go index 45b5796..d14865c 100644 --- a/binapigen/vppapi/parse_json.go +++ b/binapigen/vppapi/parse_json.go @@ -18,79 +18,52 @@ import ( "encoding/json" "errors" "fmt" + "log" "os" "strings" "github.com/bennyscetbun/jsongo" - "github.com/sirupsen/logrus" ) -var Logger *logrus.Logger - -func init() { - if strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser") { - Logger = logrus.StandardLogger() - } -} +var debug = strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser") func logf(f string, v ...interface{}) { - if Logger != nil { - Logger.Debugf(f, v...) + if debug { + log.Printf(f, v...) } } const ( - // file - objAPIVersion = "vl_api_version" - objTypes = "types" - objMessages = "messages" - objUnions = "unions" - objEnums = "enums" - objServices = "services" - objAliases = "aliases" - objOptions = "options" - objImports = "imports" - - // message - messageFieldCrc = "crc" - - // alias - aliasFieldLength = "length" - aliasFieldType = "type" - - // service - serviceFieldReply = "reply" - serviceFieldStream = "stream" - serviceFieldStreamMsg = "stream_msg" - serviceFieldEvents = "events" -) - -const ( - // file - fileOptionVersion = "version" - - // field - fieldOptionLimit = "limit" - fieldOptionDefault = "default" - + // root keys + fileAPIVersion = "vl_api_version" + fileOptions = "options" + fileTypes = "types" + fileMessages = "messages" + fileUnions = "unions" + fileEnums = "enums" + fileAliases = "aliases" + fileServices = "services" + fileImports = "imports" + // type keys + messageCrc = "crc" + enumType = "enumtype" + aliasLength = "length" + aliasType = "type" // service - serviceReplyNull = "null" + serviceReply = "reply" + serviceStream = "stream" + serviceStreamMsg = "stream_msg" + serviceEvents = "events" ) func parseJSON(data []byte) (module *File, err error) { - defer func() { - if e := recover(); e != nil { - err = fmt.Errorf("recovered panic: %v", e) - } - }() - - // parse JSON data into objects + // parse root jsonRoot := new(jsongo.Node) if err := json.Unmarshal(data, jsonRoot); err != nil { return nil, fmt.Errorf("unmarshalling JSON failed: %v", err) } - logf("file contents:") + logf("file contains:") for _, key := range jsonRoot.GetKeys() { if jsonRoot.At(key).Len() > 0 { logf(" - %2d %s", jsonRoot.At(key).Len(), key) @@ -100,38 +73,35 @@ func parseJSON(data []byte) (module *File, err error) { module = new(File) // parse CRC - if crc := jsonRoot.At(objAPIVersion); crc.GetType() == jsongo.TypeValue { - module.CRC = crc.Get().(string) + crc := jsonRoot.At(fileAPIVersion) + if crc.GetType() == jsongo.TypeValue { + module.CRC = crc.MustGetString() } // parse options - opt := jsonRoot.Map(objOptions) + opt := jsonRoot.Map(fileOptions) if opt.GetType() == jsongo.TypeMap { - module.Options = make(map[string]string, 0) + module.Options = make(map[string]string) for _, key := range opt.GetKeys() { - optionsNode := opt.At(key) optionKey := key.(string) - optionValue := optionsNode.Get().(string) - module.Options[optionKey] = optionValue + optionVal := opt.At(key).MustGetString() + module.Options[optionKey] = optionVal } } // parse imports - imports := jsonRoot.Map(objImports) - module.Imports = make([]string, 0) - imported := make(map[string]struct{}) - for i := 0; i < imports.Len(); i++ { - importNode := imports.At(i) - imp, err := parseImport(importNode) - if err != nil { - return nil, err - } - if _, ok := imported[*imp]; ok { - logf("duplicate import found: %v", *imp) + importsNode := jsonRoot.Map(fileImports) + module.Imports = make([]string, 0, importsNode.Len()) + uniq := make(map[string]struct{}) + for i := 0; i < importsNode.Len(); i++ { + importNode := importsNode.At(i) + imp := importNode.MustGetString() + if _, ok := uniq[imp]; ok { + logf("duplicate import found: %v", imp) continue } - imported[*imp] = struct{}{} - module.Imports = append(module.Imports, *imp) + uniq[imp] = struct{}{} + module.Imports = append(module.Imports, imp) } // avoid duplicate objects @@ -146,11 +116,10 @@ func parseJSON(data []byte) (module *File, err error) { } // parse enum types - enumsNode := jsonRoot.Map(objEnums) + enumsNode := jsonRoot.Map(fileEnums) module.EnumTypes = make([]EnumType, 0) for i := 0; i < enumsNode.Len(); i++ { - enumNode := enumsNode.At(i) - enum, err := parseEnum(enumNode) + enum, err := parseEnum(enumsNode.At(i)) if err != nil { return nil, err } @@ -161,13 +130,12 @@ func parseJSON(data []byte) (module *File, err error) { } // parse alias types - aliasesNode := jsonRoot.Map(objAliases) + aliasesNode := jsonRoot.Map(fileAliases) if aliasesNode.GetType() == jsongo.TypeMap { module.AliasTypes = make([]AliasType, 0) for _, key := range aliasesNode.GetKeys() { - aliasNode := aliasesNode.At(key) aliasName := key.(string) - alias, err := parseAlias(aliasName, aliasNode) + alias, err := parseAlias(aliasName, aliasesNode.At(key)) if err != nil { return nil, err } @@ -179,11 +147,10 @@ func parseJSON(data []byte) (module *File, err error) { } // parse struct types - typesNode := jsonRoot.Map(objTypes) + typesNode := jsonRoot.Map(fileTypes) module.StructTypes = make([]StructType, 0) for i := 0; i < typesNode.Len(); i++ { - typNode := typesNode.At(i) - structyp, err := parseStruct(typNode) + structyp, err := parseStruct(typesNode.At(i)) if err != nil { return nil, err } @@ -194,11 +161,10 @@ func parseJSON(data []byte) (module *File, err error) { } // parse union types - unionsNode := jsonRoot.Map(objUnions) + unionsNode := jsonRoot.Map(fileUnions) module.UnionTypes = make([]UnionType, 0) for i := 0; i < unionsNode.Len(); i++ { - unionNode := unionsNode.At(i) - union, err := parseUnion(unionNode) + union, err := parseUnion(unionsNode.At(i)) if err != nil { return nil, err } @@ -209,12 +175,11 @@ func parseJSON(data []byte) (module *File, err error) { } // parse messages - messagesNode := jsonRoot.Map(objMessages) + messagesNode := jsonRoot.Map(fileMessages) if messagesNode.GetType() == jsongo.TypeArray { module.Messages = make([]Message, messagesNode.Len()) for i := 0; i < messagesNode.Len(); i++ { - msgNode := messagesNode.At(i) - msg, err := parseMessage(msgNode) + msg, err := parseMessage(messagesNode.At(i)) if err != nil { return nil, err } @@ -223,15 +188,14 @@ func parseJSON(data []byte) (module *File, err error) { } // parse services - servicesNode := jsonRoot.Map(objServices) + servicesNode := jsonRoot.Map(fileServices) if servicesNode.GetType() == jsongo.TypeMap { module.Service = &Service{ RPCs: make([]RPC, servicesNode.Len()), } for i, key := range servicesNode.GetKeys() { - rpcNode := servicesNode.At(key) rpcName := key.(string) - svc, err := parseServiceRPC(rpcName, rpcNode) + svc, err := parseServiceRPC(rpcName, servicesNode.At(key)) if err != nil { return nil, err } @@ -242,20 +206,6 @@ func parseJSON(data []byte) (module *File, err error) { return module, nil } -// parseImport parses VPP binary API import from JSON node -func parseImport(importNode *jsongo.Node) (*string, error) { - if importNode.GetType() != jsongo.TypeValue { - return nil, errors.New("invalid JSON for import specified") - } - - importName, ok := importNode.Get().(string) - if !ok { - return nil, fmt.Errorf("import name is %T, not a string", importNode.Get()) - } - - return &importName, nil -} - // parseEnum parses VPP binary API enum object from JSON node func parseEnum(enumNode *jsongo.Node) (*EnumType, error) { if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue { @@ -266,7 +216,7 @@ func parseEnum(enumNode *jsongo.Node) (*EnumType, error) { if !ok { return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get()) } - enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string) + enumType, ok := enumNode.At(enumNode.Len() - 1).At(enumType).Get().(string) if !ok { return nil, fmt.Errorf("enum type invalid or missing") } @@ -367,7 +317,7 @@ func parseStruct(typeNode *jsongo.Node) (*StructType, error) { // parseAlias parses VPP binary API alias object from JSON node func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) { - if aliasNode.Len() == 0 || aliasNode.At(aliasFieldType).GetType() != jsongo.TypeValue { + if aliasNode.Len() == 0 || aliasNode.At(aliasType).GetType() != jsongo.TypeValue { return nil, errors.New("invalid JSON for alias specified") } @@ -375,7 +325,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) { Name: aliasName, } - if typeNode := aliasNode.At(aliasFieldType); typeNode.GetType() == jsongo.TypeValue { + if typeNode := aliasNode.At(aliasType); typeNode.GetType() == jsongo.TypeValue { typ, ok := typeNode.Get().(string) if !ok { return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get()) @@ -385,7 +335,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) { } } - if lengthNode := aliasNode.At(aliasFieldLength); lengthNode.GetType() == jsongo.TypeValue { + if lengthNode := aliasNode.At(aliasLength); lengthNode.GetType() == jsongo.TypeValue { length, ok := lengthNode.Get().(float64) if !ok { return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get()) @@ -398,7 +348,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) { // parseMessage parses VPP binary API message object from JSON node func parseMessage(msgNode *jsongo.Node) (*Message, error) { - if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue { + if msgNode.Len() < 2 || msgNode.At(0).GetType() != jsongo.TypeValue { return nil, errors.New("invalid JSON for message specified") } @@ -406,9 +356,8 @@ func parseMessage(msgNode *jsongo.Node) (*Message, error) { if !ok { return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get()) } - msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageFieldCrc).Get().(string) + msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageCrc).Get().(string) if !ok { - return nil, fmt.Errorf("message crc invalid or missing") } @@ -466,26 +415,16 @@ func parseField(field *jsongo.Node) (*Field, error) { case jsongo.TypeMap: fieldMeta := field.At(2) - + if fieldMeta.Len() == 0 { + break + } + f.Meta = map[string]interface{}{} for _, key := range fieldMeta.GetKeys() { - metaNode := fieldMeta.At(key) metaName := key.(string) - metaValue := metaNode.Get() - - switch metaName { - case fieldOptionLimit: - metaValue = int(metaNode.Get().(float64)) - case fieldOptionDefault: - metaValue = metaNode.Get() - default: - logrus.Warnf("unknown meta info (%s=%v) for field (%s)", metaName, metaValue, fieldName) - } - - if f.Meta == nil { - f.Meta = map[string]interface{}{} - } + metaValue := fieldMeta.At(key).Get() f.Meta[metaName] = metaValue } + default: return nil, errors.New("invalid JSON for field specified") } @@ -503,27 +442,24 @@ func parseField(field *jsongo.Node) (*Field, error) { // parseServiceRPC parses VPP binary API service object from JSON node func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) { - if rpcNode.Len() == 0 || rpcNode.At(serviceFieldReply).GetType() != jsongo.TypeValue { + if rpcNode.Len() == 0 || rpcNode.At(serviceReply).GetType() != jsongo.TypeValue { return nil, errors.New("invalid JSON for service RPC specified") } rpc := RPC{ - Name: rpcName, - RequestMsg: rpcName, + Request: rpcName, } - if replyNode := rpcNode.At(serviceFieldReply); replyNode.GetType() == jsongo.TypeValue { + if replyNode := rpcNode.At(serviceReply); replyNode.GetType() == jsongo.TypeValue { reply, ok := replyNode.Get().(string) if !ok { return nil, fmt.Errorf("service RPC reply is %T, not a string", replyNode.Get()) } - if reply != serviceReplyNull { - rpc.ReplyMsg = reply - } + rpc.Reply = reply } // is stream (dump) - if streamNode := rpcNode.At(serviceFieldStream); streamNode.GetType() == jsongo.TypeValue { + if streamNode := rpcNode.At(serviceStream); streamNode.GetType() == jsongo.TypeValue { var ok bool rpc.Stream, ok = streamNode.Get().(bool) if !ok { @@ -532,7 +468,7 @@ func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) { } // stream message - if streamMsgNode := rpcNode.At(serviceFieldStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue { + if streamMsgNode := rpcNode.At(serviceStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue { var ok bool rpc.StreamMsg, ok = streamMsgNode.Get().(string) if !ok { @@ -541,7 +477,7 @@ func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) { } // events service (event subscription) - if eventsNode := rpcNode.At(serviceFieldEvents); eventsNode.GetType() == jsongo.TypeArray { + if eventsNode := rpcNode.At(serviceEvents); eventsNode.GetType() == jsongo.TypeArray { for j := 0; j < eventsNode.Len(); j++ { event := eventsNode.At(j).Get().(string) rpc.Events = append(rpc.Events, event) diff --git a/binapigen/vppapi/util.go b/binapigen/vppapi/util.go new file mode 100644 index 0000000..87f2e55 --- /dev/null +++ b/binapigen/vppapi/util.go @@ -0,0 +1,112 @@ +// Copyright (c) 2020 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vppapi + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "strings" + + "github.com/sirupsen/logrus" +) + +const ( + VPPVersionEnvVar = "VPP_VERSION" +) + +// ResolveVPPVersion resolves version of the VPP for target directory. +// +// Version resolved here can be overriden by setting VPP_VERSION env var. +func ResolveVPPVersion(apidir string) string { + // check env variable override + if ver := os.Getenv(VPPVersionEnvVar); ver != "" { + logrus.Debugf("VPP version was manually set to %q via %s env var", ver, VPPVersionEnvVar) + return ver + } + + // assuming VPP package is installed + if path.Clean(apidir) == DefaultDir { + version, err := GetVPPVersionInstalled() + if err != nil { + logrus.Warnf("resolving VPP version from installed package failed: %v", err) + } else { + logrus.Debugf("resolved VPP version from installed package: %v", version) + return version + } + } + + // check if inside VPP repo + repoDir, err := findGitRepoRootDir(apidir) + if err != nil { + logrus.Warnf("checking VPP git repo failed: %v", err) + } else { + logrus.Debugf("resolved git repo root directory: %v", repoDir) + version, err := GetVPPVersionRepo(repoDir) + if err != nil { + logrus.Warnf("resolving VPP version from version script failed: %v", err) + } else { + logrus.Debugf("resolved VPP version from version script: %v", version) + return version + } + } + + // try to read VPP_VERSION file + data, err := ioutil.ReadFile(path.Join(repoDir, "VPP_VERSION")) + if err == nil { + return strings.TrimSpace(string(data)) + } + + logrus.Warnf("VPP version could not be resolved, you can set it manually using %s env var", VPPVersionEnvVar) + return "" +} + +// GetVPPVersionInstalled retrieves VPP version of installed package using dpkg-query. +func GetVPPVersionInstalled() (string, error) { + cmd := exec.Command("dpkg-query", "-f", "${Version}", "-W", "vpp") + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("dpkg-query command failed: %v\noutput: %s", err, out) + } + return strings.TrimSpace(string(out)), nil +} + +const versionScriptPath = "./src/scripts/version" + +// GetVPPVersionRepo retrieves VPP version using script in repo directory. +func GetVPPVersionRepo(repoDir string) (string, error) { + if _, err := os.Stat(versionScriptPath); err != nil { + return "", err + } + cmd := exec.Command(versionScriptPath) + cmd.Dir = repoDir + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("version script failed: %v\noutput: %s", err, out) + } + return strings.TrimSpace(string(out)), nil +} + +func findGitRepoRootDir(dir string) (string, error) { + cmd := exec.Command("git", "rev-parse", "--show-toplevel") + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("git command failed: %v\noutput: %s", err, out) + } + return strings.TrimSpace(string(out)), nil +} diff --git a/binapigen/vppapi/parser.go b/binapigen/vppapi/vppapi.go index 312dd0e..665fa81 100644 --- a/binapigen/vppapi/parser.go +++ b/binapigen/vppapi/vppapi.go @@ -19,18 +19,15 @@ import ( "io/ioutil" "path/filepath" "strings" - - "github.com/sirupsen/logrus" ) const ( - DefaultAPIDir = "/usr/share/vpp/api" + // DefaultDir is default location of API files. + DefaultDir = "/usr/share/vpp/api" ) -const apifileSuffixJson = ".api.json" - -// FindFiles returns all input files located in specified directory -func FindFiles(dir string, deep int) (paths []string, err error) { +// FindFiles finds API files located in dir or in a nested directory that is not nested deeper than deep. +func FindFiles(dir string, deep int) (files []string, err error) { entries, err := ioutil.ReadDir(dir) if err != nil { return nil, fmt.Errorf("reading directory %s failed: %v", dir, err) @@ -41,43 +38,44 @@ func FindFiles(dir string, deep int) (paths []string, err error) { if nested, err := FindFiles(nestedDir, deep-1); err != nil { return nil, err } else { - paths = append(paths, nested...) + files = append(files, nested...) } - } else if strings.HasSuffix(e.Name(), apifileSuffixJson) { - paths = append(paths, filepath.Join(dir, e.Name())) + } else if !e.IsDir() && strings.HasSuffix(e.Name(), ".api.json") { + files = append(files, filepath.Join(dir, e.Name())) } } - return paths, nil + return files, nil } +// Parse parses API files in directory DefaultDir. func Parse() ([]*File, error) { - return ParseDir(DefaultAPIDir) + return ParseDir(DefaultDir) } +// ParseDir finds and parses API files in given directory and returns parsed files. +// Supports API files in JSON format (.api.json) only. func ParseDir(apidir string) ([]*File, error) { - files, err := FindFiles(apidir, 1) + list, err := FindFiles(apidir, 1) if err != nil { return nil, err } - logrus.Infof("found %d files in API dir %q", len(files), apidir) - - var modules []*File + logf("found %d files in API dir %q", len(list), apidir) - for _, file := range files { + var files []*File + for _, file := range list { module, err := ParseFile(file) if err != nil { return nil, err } - modules = append(modules, module) + files = append(files, module) } - - return modules, nil + return files, nil } -// ParseFile parses API file contents and returns File. +// ParseFile parses API file and returns File. func ParseFile(apifile string) (*File, error) { - if !strings.HasSuffix(apifile, apifileSuffixJson) { + if !strings.HasSuffix(apifile, ".api.json") { return nil, fmt.Errorf("unsupported file format: %q", apifile) } @@ -101,7 +99,14 @@ func ParseFile(apifile string) (*File, error) { return module, nil } +// ParseRaw parses raw API file data and returns File. func ParseRaw(data []byte) (file *File, err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("panic occurred: %v", e) + } + }() + file, err = parseJSON(data) if err != nil { return nil, err diff --git a/binapigen/vppapi/parser_test.go b/binapigen/vppapi/vppapi_test.go index 2dc82e4..027cc1f 100644 --- a/binapigen/vppapi/parser_test.go +++ b/binapigen/vppapi/vppapi_test.go @@ -46,7 +46,7 @@ func TestReadJson(t *testing.T) { inputData, err := ioutil.ReadFile("testdata/af_packet.api.json") Expect(err).ShouldNot(HaveOccurred()) - result, err := parseJSON(inputData) + result, err := ParseRaw(inputData) Expect(err).ShouldNot(HaveOccurred()) Expect(result).ToNot(BeNil()) Expect(result.EnumTypes).To(HaveLen(0)) @@ -60,7 +60,7 @@ func TestReadJsonError(t *testing.T) { inputData, err := ioutil.ReadFile("testdata/input-read-json-error.json") Expect(err).ShouldNot(HaveOccurred()) - result, err := parseJSON(inputData) + result, err := ParseRaw(inputData) Expect(err).Should(HaveOccurred()) Expect(result).To(BeNil()) } @@ -80,17 +80,21 @@ func TestParseFile(t *testing.T) { if module.Name != "vpe" { t.Errorf("expected Name=%s, got %v", "vpe", module.Name) } + if module.Path != "testdata/vpe.api.json" { + t.Errorf("expected Path=%s, got %v", "testdata/vpe.api.json", module.Path) + } if module.CRC != "0xbd2c94f4" { t.Errorf("expected CRC=%s, got %v", "0xbd2c94f4", module.CRC) } - if module.Version() != "1.6.1" { - t.Errorf("expected Version=%s, got %v", "1.6.1", module.Version()) + + if version := module.Options["version"]; version != "1.6.1" { + t.Errorf("expected option[version]=%s, got %v", "1.6.1", version) } if len(module.Imports) == 0 { t.Errorf("expected imports, got none") } - if len(module.Options) == 0 { - t.Errorf("expected options, got none") + if len(module.EnumTypes) == 0 { + t.Errorf("expected enums, got none") } if len(module.AliasTypes) == 0 { t.Errorf("expected aliases, got none") @@ -98,12 +102,12 @@ func TestParseFile(t *testing.T) { if len(module.StructTypes) == 0 { t.Errorf("expected types, got none") } - if len(module.Service.RPCs) == 0 { - t.Errorf("expected service method, got none") - } if len(module.Messages) == 0 { t.Errorf("expected messages, got none") } + if len(module.Service.RPCs) == 0 { + t.Errorf("expected service RPCs, got none") + } } func TestParseFileUnsupported(t *testing.T) { |