Skip to content

Commit

Permalink
Merge pull request #8 from nvima/feat-better-error-handling
Browse files Browse the repository at this point in the history
Improve error handling and add new test cases
  • Loading branch information
nvima authored Apr 22, 2023
2 parents f16b891 + d2e26b0 commit ddaecd5
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 38 deletions.
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ For example, the following command:
reads from the YAML configuration file, processes the "translate" function, and sends an API request to an API.
The response is then printed to stdout. For more Information about YAML Configuration, visit https://github.com/nvima/httpcli.`,
Run: tplCommand,
RunE: tplCommand,
}

func Execute() {
Expand Down
12 changes: 12 additions & 0 deletions cmd/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,15 @@ translate:
env:
- "TEST_API_KEY"
- "TEST_SERVER_URL"
emptydata:
url: "${TEST_SERVER_URL}/emptydata"
output: "response"
env:
- "TEST_SERVER_URL"
emptyoutput:
url: "${TEST_SERVER_URL}/emptydata"
env:
- "TEST_SERVER_URL"
emptyurl:
env:
- "TEST_SERVER_URL"
50 changes: 32 additions & 18 deletions cmd/tpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,51 @@ type AppConfig struct {
Functions map[string]FunctionConfig
}

//TODO Better error handling for testing
func tplCommand(cmd *cobra.Command, args []string) {
fc := initFunctionConfig(cmd, args)
output := fc.handleFunc(cmd)
var fc FunctionConfig

func tplCommand(cmd *cobra.Command, args []string) error {
err := initFunctionConfig(cmd, args)
if err != nil {
return err
}

output, err := fc.handleFunc(cmd)
if err != nil {
return err
}

fmt.Fprintf(cmd.OutOrStdout(), output)
return nil
}

func initFunctionConfig(cmd *cobra.Command, args []string) FunctionConfig {
fc := FunctionConfig{}
func initFunctionConfig(cmd *cobra.Command, args []string) error {
config := viper.AllSettings()

if len(config) == 0 {
util.HandleError(cmd, util.NO_FUNC_NAME_ERR.Err(), util.NO_CONFIG_FILE_ERR)
return util.HandleError(cmd, util.NO_FUNC_NAME_ERR.Err(), util.NO_CONFIG_FILE_ERR)
}

if len(args) == 0 {
util.HandleError(cmd, util.NO_FUNC_NAME_ERR.Err(), util.NO_FUNC_NAME_ERR)
return util.HandleError(cmd, util.NO_FUNC_NAME_ERR.Err(), util.NO_FUNC_NAME_ERR)
}

var appConfig AppConfig
err := mapstructure.Decode(config, &appConfig.Functions)
if err != nil {
util.HandleError(cmd, err, util.INVALID_CONFIG_ERR)
return util.HandleError(cmd, err, util.INVALID_CONFIG_ERR)
}

fc, ok := appConfig.Functions[args[0]]
funcConfig, ok := appConfig.Functions[args[0]]
if !ok {
util.HandleError(cmd, util.NO_FUNC_FOUND_ERR.Err(), util.NO_FUNC_FOUND_ERR)
return util.HandleError(cmd, util.NO_FUNC_FOUND_ERR.Err(), util.NO_FUNC_FOUND_ERR)
}

if funcConfig.Url == "" {
return util.HandleError(cmd, util.NO_URL_ERR.Err(), util.NO_URL_ERR)
}

return fc
fc = funcConfig
return nil
}

func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) ([]byte, error) {
Expand Down Expand Up @@ -97,28 +111,28 @@ func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) ([]b
return body, nil
}

func (fc *FunctionConfig) handleFunc(cmd *cobra.Command) string {
func (fc *FunctionConfig) handleFunc(cmd *cobra.Command) (string, error) {
jsonData, err := fc.getJSONData()
if err != nil {
util.HandleError(cmd, err, util.FAILED_TO_GET_DATA)
return "", util.HandleError(cmd, err, util.FAILED_TO_GET_DATA)
}

body, err := fc.makeHttpCall(jsonData, cmd)
if err != nil {
util.HandleError(cmd, err, util.FAILED_TO_MAKE_HTTP_CALL)
return "", util.HandleError(cmd, err, util.FAILED_TO_MAKE_HTTP_CALL)
}

responseData, err := util.ParseJSONResponse(body)
if err != nil {
util.HandleError(cmd, err, util.FAILED_TO_PARSE_JSON)
return "", util.HandleError(cmd, err, util.FAILED_TO_PARSE_JSON)
}

output, err := util.GetOutputField(responseData, fc.Output)
if err != nil {
util.HandleError(cmd, err, util.FAILED_TO_PARSE_OUTPUT_FIELD)
return "", util.HandleError(cmd, err, util.FAILED_TO_PARSE_OUTPUT_FIELD)
}

return output
return output, nil
}

func (fc *FunctionConfig) getJSONData() ([]byte, error) {
Expand Down
43 changes: 43 additions & 0 deletions cmd/tpl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"testing"

"github.com/nvima/httpcli/util"
)

var testServer *httptest.Server
Expand Down Expand Up @@ -38,6 +39,8 @@ func startTestHTTPServer() *httptest.Server {
w.Write([]byte(`{"choices": [{"message": {"content": "` + data.Messages[0].Content + `"}}]}`))
case "/v2/translate":
w.Write([]byte(`{"translations": [{"text": "Pomme"}]}`))
case "/emptydata":
w.Write([]byte(`{"response":"Hello"}`))
default:
http.Error(w, "Not found", http.StatusNotFound)
}
Expand Down Expand Up @@ -75,3 +78,43 @@ func TestTranslate(t *testing.T) {
t.Fatalf("expected \"%s\" got \"%s\"", expected, string(out))
}
}

func TestEmptyData(t *testing.T) {
b := bytes.NewBufferString("")
rootCmd.SetOut(b)
rootCmd.SetArgs([]string{"emptydata", "--config", "./testdata/config.yaml"})
rootCmd.Execute()
out, err := ioutil.ReadAll(b)
if err != nil {
t.Fatal(err)
}
expected := "Hello"
if string(out) != expected {
t.Fatalf("expected \"%s\" got \"%s\"", expected, string(out))
}
}

func TestEmptyOutput(t *testing.T) {
b := bytes.NewBufferString("")
rootCmd.SetOut(b)
rootCmd.SetArgs([]string{"emptyoutput", "--config", "./testdata/config.yaml"})
rootCmd.Execute()
out, err := ioutil.ReadAll(b)
if err != nil {
t.Fatal(err)
}
expected := []byte(`{"response":"Hello"}`)
if string(out) != string(expected) {
t.Fatalf("expected \"%s\" got \"%s\"", expected, string(out))
}
}

func TestEmptyUrl(t *testing.T) {
b := bytes.NewBufferString("")
rootCmd.SetOut(b)
rootCmd.SetArgs([]string{"emptyurl", "--config", "./testdata/config.yaml"})
err := rootCmd.Execute()
if err.Error() != util.NO_URL_ERR.Msg() {
t.Fatalf("expected \"%s\" got \"%s\"", util.NO_URL_ERR.Msg(), err.Error())
}
}
17 changes: 3 additions & 14 deletions util/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
type TplError struct {
msg string
err error
exitCode int
}

func (te TplError) Err() error {
Expand All @@ -18,46 +17,36 @@ func (te TplError) Msg() string {
return te.msg
}

func (te TplError) ExitCode() int {
return te.exitCode
}

var NO_CONFIG_FILE_ERR = TplError{
msg: "No config file found",
exitCode: 1,
}

var NO_FUNC_NAME_ERR = TplError{
msg: "No function name provided",
exitCode: 2,
}
var NO_FUNC_FOUND_ERR = TplError{
msg: "Function not found in config",
exitCode: 3,
}

var INVALID_CONFIG_ERR = TplError{
msg: "Invalid config file",
exitCode: 4,
}

var INVALID_RESP_CODE = TplError{
msg: "Invalid response code",
exitCode: 5,
}
var FAILED_TO_GET_DATA = TplError{
msg: "Failed to get data",
exitCode: 6,
}
var FAILED_TO_MAKE_HTTP_CALL = TplError{
msg: "Failed to make http call",
exitCode: 7,
}
var FAILED_TO_PARSE_JSON = TplError{
msg: "Failed to parse JSON",
exitCode: 8,
}
var FAILED_TO_PARSE_OUTPUT_FIELD = TplError{
msg: "Failed to parse output field",
exitCode: 9,
}
var NO_URL_ERR = TplError{
msg: "No URL provided",
}
11 changes: 6 additions & 5 deletions util/handleError.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package util

import (
"fmt"
"os"
// "fmt"

"github.com/spf13/cobra"
)

func HandleError(cmd *cobra.Command, err error, tplError TplError) {
func HandleError(cmd *cobra.Command, err error, tplError TplError) error {
//TODO:: Add Error Logging with Stacktraces
// fmt.Println(err)
fmt.Fprintf(cmd.OutOrStdout(), tplError.msg)
os.Exit(tplError.exitCode)

// fmt.Fprintf(cmd.OutOrStdout(), tplError.msg)
return err
}

0 comments on commit ddaecd5

Please sign in to comment.