Skip to content

Commit

Permalink
Merge pull request #9 from nvima/feat-better-error-handling
Browse files Browse the repository at this point in the history
Add debug flag and refactor error handling
  • Loading branch information
nvima authored Apr 23, 2023
2 parents ddaecd5 + 0b4eb99 commit 28a4f17
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 104 deletions.
12 changes: 6 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ 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.`,
RunE: tplCommand,
RunE: tplCommand,
SilenceUsage: true,
SilenceErrors: true,
}

func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
func Execute() error {
return rootCmd.Execute()
}

func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.httpcli.yaml)")
rootCmd.PersistentFlags().Bool("debug", false, "Enable debug mode for error logging")
}

func initConfig() {
Expand Down
5 changes: 5 additions & 0 deletions cmd/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ emptyoutput:
emptyurl:
env:
- "TEST_SERVER_URL"
wrongstatuscode:
url: "${TEST_SERVER_URL}/nonexistentpath"
statuscode: 200
env:
- "TEST_SERVER_URL"
76 changes: 37 additions & 39 deletions cmd/tpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ type AppConfig struct {
Functions map[string]FunctionConfig
}

var fc FunctionConfig

func tplCommand(cmd *cobra.Command, args []string) error {
err := initFunctionConfig(cmd, args)
fc, err := initFunctionConfig(cmd, args)
if err != nil {
return err
}
Expand All @@ -45,41 +43,59 @@ func tplCommand(cmd *cobra.Command, args []string) error {
return nil
}

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

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

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

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

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

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

fc = funcConfig
return nil
return funcConfig, nil
}

func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) ([]byte, error) {
func (fc *FunctionConfig) handleFunc(cmd *cobra.Command) (string, error) {
jsonData, err := fc.getJSONData(cmd)
if err != nil {
return "", err
}

response, err := fc.makeHttpCall(jsonData, cmd)
if err != nil {
return "", err
}

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

return output, nil
}

func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) (map[string]interface{}, error) {
url := fc.replaceEnvVariables(fc.Url)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
return nil, util.HandleError(cmd, err, util.INIT_HTTP_POST_REQUEST_FAILED)
}

for _, header := range fc.Header {
Expand All @@ -94,56 +110,38 @@ func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) ([]b
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
return nil, util.HandleError(cmd, err, util.SEND_HTTP_POST_REQUEST_FAILED)
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
return nil, util.HandleError(cmd, err, util.READ_RESPONSE_BODY_FAILED)
}
defer resp.Body.Close()

// Check if the request was successful when a status code is provided
if fc.StatusCode != 0 && resp.StatusCode != fc.StatusCode {
err := fmt.Errorf("Request failed with status code %d, Body: %s", resp.StatusCode, string(body))
util.HandleError(cmd, err, util.INVALID_RESP_CODE)
}
return body, nil
}

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

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

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

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

return output, nil
return responseData, nil
}

func (fc *FunctionConfig) getJSONData() ([]byte, error) {
func (fc *FunctionConfig) getJSONData(cmd *cobra.Command) ([]byte, error) {
jsonData, err := json.Marshal(fc.Data)
if err != nil {
return nil, err
return nil, util.HandleError(cmd, err, util.MARSHAL_DATA_FAILED)
}

jsonData, err = util.ReplaceStdIn(jsonData)
if err != nil {
return nil, err
return nil, util.HandleError(cmd, err, util.REPLACE_STDIN_FAILED)
}

return jsonData, nil
Expand Down
14 changes: 12 additions & 2 deletions cmd/tpl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,17 @@ func TestEmptyUrl(t *testing.T) {
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())
if err.Error() != util.NO_URL_ERR.Error() {
t.Fatalf("expected \"%s\" got \"%s\"", util.NO_URL_ERR.Error(), err.Error())
}
}

func TestWrongStatusCode(t *testing.T) {
b := bytes.NewBufferString("")
rootCmd.SetOut(b)
rootCmd.SetArgs([]string{"wrongstatuscode", "--config", "./testdata/config.yaml"})
err := rootCmd.Execute()
if err.Error() != util.INVALID_RESP_CODE.Error() {
t.Fatalf("expected \"%s\" got \"%s\"", util.INVALID_RESP_CODE.Error(), err.Error())
}
}
12 changes: 9 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package main

import "github.com/nvima/httpcli/cmd"
import (
"os"

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

func main() {
cmd.Execute()
err := cmd.Execute()
if err != nil {
os.Exit(1)
}
}

63 changes: 16 additions & 47 deletions util/errors.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,21 @@
package util

import (
"fmt"
"errors"
)

type TplError struct {
msg string
err error
}

func (te TplError) Err() error {
return fmt.Errorf(te.msg)
}

func (te TplError) Msg() string {
return te.msg
}

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

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

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

var INVALID_RESP_CODE = TplError{
msg: "Invalid response code",
}
var FAILED_TO_GET_DATA = TplError{
msg: "Failed to get data",
}
var FAILED_TO_MAKE_HTTP_CALL = TplError{
msg: "Failed to make http call",
}
var FAILED_TO_PARSE_JSON = TplError{
msg: "Failed to parse JSON",
}
var FAILED_TO_PARSE_OUTPUT_FIELD = TplError{
msg: "Failed to parse output field",
}
var NO_URL_ERR = TplError{
msg: "No URL provided",
}
var NO_CONFIG_FILE_ERR = errors.New("No config file found")
var NO_FUNC_NAME_ERR = errors.New("No function name provided")
var NO_FUNC_FOUND_ERR = errors.New("Function not found in config")
var INVALID_CONFIG_ERR = errors.New("Invalid config file")
var INVALID_RESP_CODE = errors.New("Invalid response code")
var FAILED_TO_GET_DATA = errors.New("Failed to get data")
var FAILED_TO_MAKE_HTTP_CALL = errors.New("Failed to make http call")
var FAILED_TO_PARSE_JSON_RESPONSE = errors.New("Failed to parse JSON Response")
var FAILED_TO_PARSE_OUTPUT_FIELD = errors.New("Failed to parse output field")
var NO_URL_ERR = errors.New("No URL provided")
var MARSHAL_DATA_FAILED = errors.New("Failed to marshal data")
var REPLACE_STDIN_FAILED = errors.New("Failed to replace stdin")
var INIT_HTTP_POST_REQUEST_FAILED = errors.New("An error occurred while initializing the HTTP POST request. Possible causes could be an invalid URL or incorrect input parameters.")
var SEND_HTTP_POST_REQUEST_FAILED = errors.New("An error occurred while sending the HTTP POST request. Possible causes could be network issues, server unavailability, or issues with the request payload.")
var READ_RESPONSE_BODY_FAILED = errors.New("Failed to read the response body")
14 changes: 7 additions & 7 deletions util/handleError.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package util

import (
// "fmt"
"fmt"

"github.com/spf13/cobra"
)

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)
return err
func HandleError(cmd *cobra.Command, err error, tplError error) error {
debug, _ := cmd.Flags().GetBool("debug")
if debug {
fmt.Printf("Debug: %v\n", err)
}
return tplError
}

0 comments on commit 28a4f17

Please sign in to comment.