Skip to content

Commit 28a4f17

Browse files
authored
Merge pull request #9 from nvima/feat-better-error-handling
Add debug flag and refactor error handling
2 parents ddaecd5 + 0b4eb99 commit 28a4f17

File tree

7 files changed

+92
-104
lines changed

7 files changed

+92
-104
lines changed

cmd/root.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ For example, the following command:
2323
2424
reads from the YAML configuration file, processes the "translate" function, and sends an API request to an API.
2525
The response is then printed to stdout. For more Information about YAML Configuration, visit https://github.com/nvima/httpcli.`,
26-
RunE: tplCommand,
26+
RunE: tplCommand,
27+
SilenceUsage: true,
28+
SilenceErrors: true,
2729
}
2830

29-
func Execute() {
30-
err := rootCmd.Execute()
31-
if err != nil {
32-
os.Exit(1)
33-
}
31+
func Execute() error {
32+
return rootCmd.Execute()
3433
}
3534

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

4141
func initConfig() {

cmd/testdata/config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ emptyoutput:
3838
emptyurl:
3939
env:
4040
- "TEST_SERVER_URL"
41+
wrongstatuscode:
42+
url: "${TEST_SERVER_URL}/nonexistentpath"
43+
statuscode: 200
44+
env:
45+
- "TEST_SERVER_URL"

cmd/tpl.go

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ type AppConfig struct {
2828
Functions map[string]FunctionConfig
2929
}
3030

31-
var fc FunctionConfig
32-
3331
func tplCommand(cmd *cobra.Command, args []string) error {
34-
err := initFunctionConfig(cmd, args)
32+
fc, err := initFunctionConfig(cmd, args)
3533
if err != nil {
3634
return err
3735
}
@@ -45,41 +43,59 @@ func tplCommand(cmd *cobra.Command, args []string) error {
4543
return nil
4644
}
4745

48-
func initFunctionConfig(cmd *cobra.Command, args []string) error {
46+
func initFunctionConfig(cmd *cobra.Command, args []string) (FunctionConfig, error) {
4947
config := viper.AllSettings()
5048

5149
if len(config) == 0 {
52-
return util.HandleError(cmd, util.NO_FUNC_NAME_ERR.Err(), util.NO_CONFIG_FILE_ERR)
50+
return FunctionConfig{}, util.HandleError(cmd, util.NO_FUNC_NAME_ERR, util.NO_CONFIG_FILE_ERR)
5351
}
5452

5553
if len(args) == 0 {
56-
return util.HandleError(cmd, util.NO_FUNC_NAME_ERR.Err(), util.NO_FUNC_NAME_ERR)
54+
return FunctionConfig{}, util.HandleError(cmd, util.NO_FUNC_NAME_ERR, util.NO_FUNC_NAME_ERR)
5755
}
5856

5957
var appConfig AppConfig
6058
err := mapstructure.Decode(config, &appConfig.Functions)
6159
if err != nil {
62-
return util.HandleError(cmd, err, util.INVALID_CONFIG_ERR)
60+
return FunctionConfig{}, util.HandleError(cmd, err, util.INVALID_CONFIG_ERR)
6361
}
6462

6563
funcConfig, ok := appConfig.Functions[args[0]]
6664
if !ok {
67-
return util.HandleError(cmd, util.NO_FUNC_FOUND_ERR.Err(), util.NO_FUNC_FOUND_ERR)
65+
return FunctionConfig{}, util.HandleError(cmd, util.NO_FUNC_FOUND_ERR, util.NO_FUNC_FOUND_ERR)
6866
}
6967

7068
if funcConfig.Url == "" {
71-
return util.HandleError(cmd, util.NO_URL_ERR.Err(), util.NO_URL_ERR)
69+
return FunctionConfig{}, util.HandleError(cmd, util.NO_URL_ERR, util.NO_URL_ERR)
7270
}
7371

74-
fc = funcConfig
75-
return nil
72+
return funcConfig, nil
7673
}
7774

78-
func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) ([]byte, error) {
75+
func (fc *FunctionConfig) handleFunc(cmd *cobra.Command) (string, error) {
76+
jsonData, err := fc.getJSONData(cmd)
77+
if err != nil {
78+
return "", err
79+
}
80+
81+
response, err := fc.makeHttpCall(jsonData, cmd)
82+
if err != nil {
83+
return "", err
84+
}
85+
86+
output, err := util.GetOutputField(response, fc.Output)
87+
if err != nil {
88+
return "", util.HandleError(cmd, err, util.FAILED_TO_PARSE_OUTPUT_FIELD)
89+
}
90+
91+
return output, nil
92+
}
93+
94+
func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) (map[string]interface{}, error) {
7995
url := fc.replaceEnvVariables(fc.Url)
8096
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
8197
if err != nil {
82-
return nil, err
98+
return nil, util.HandleError(cmd, err, util.INIT_HTTP_POST_REQUEST_FAILED)
8399
}
84100

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

100116
body, err := ioutil.ReadAll(resp.Body)
101117
if err != nil {
102-
return nil, err
118+
return nil, util.HandleError(cmd, err, util.READ_RESPONSE_BODY_FAILED)
103119
}
104120
defer resp.Body.Close()
105121

106122
// Check if the request was successful when a status code is provided
107123
if fc.StatusCode != 0 && resp.StatusCode != fc.StatusCode {
108124
err := fmt.Errorf("Request failed with status code %d, Body: %s", resp.StatusCode, string(body))
109-
util.HandleError(cmd, err, util.INVALID_RESP_CODE)
110-
}
111-
return body, nil
112-
}
113-
114-
func (fc *FunctionConfig) handleFunc(cmd *cobra.Command) (string, error) {
115-
jsonData, err := fc.getJSONData()
116-
if err != nil {
117-
return "", util.HandleError(cmd, err, util.FAILED_TO_GET_DATA)
118-
}
119-
120-
body, err := fc.makeHttpCall(jsonData, cmd)
121-
if err != nil {
122-
return "", util.HandleError(cmd, err, util.FAILED_TO_MAKE_HTTP_CALL)
125+
return nil, util.HandleError(cmd, err, util.INVALID_RESP_CODE)
123126
}
124127

125128
responseData, err := util.ParseJSONResponse(body)
126129
if err != nil {
127-
return "", util.HandleError(cmd, err, util.FAILED_TO_PARSE_JSON)
130+
return nil, util.HandleError(cmd, err, util.FAILED_TO_PARSE_JSON_RESPONSE)
128131
}
129132

130-
output, err := util.GetOutputField(responseData, fc.Output)
131-
if err != nil {
132-
return "", util.HandleError(cmd, err, util.FAILED_TO_PARSE_OUTPUT_FIELD)
133-
}
134-
135-
return output, nil
133+
return responseData, nil
136134
}
137135

138-
func (fc *FunctionConfig) getJSONData() ([]byte, error) {
136+
func (fc *FunctionConfig) getJSONData(cmd *cobra.Command) ([]byte, error) {
139137
jsonData, err := json.Marshal(fc.Data)
140138
if err != nil {
141-
return nil, err
139+
return nil, util.HandleError(cmd, err, util.MARSHAL_DATA_FAILED)
142140
}
143141

144142
jsonData, err = util.ReplaceStdIn(jsonData)
145143
if err != nil {
146-
return nil, err
144+
return nil, util.HandleError(cmd, err, util.REPLACE_STDIN_FAILED)
147145
}
148146

149147
return jsonData, nil

cmd/tpl_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,17 @@ func TestEmptyUrl(t *testing.T) {
114114
rootCmd.SetOut(b)
115115
rootCmd.SetArgs([]string{"emptyurl", "--config", "./testdata/config.yaml"})
116116
err := rootCmd.Execute()
117-
if err.Error() != util.NO_URL_ERR.Msg() {
118-
t.Fatalf("expected \"%s\" got \"%s\"", util.NO_URL_ERR.Msg(), err.Error())
117+
if err.Error() != util.NO_URL_ERR.Error() {
118+
t.Fatalf("expected \"%s\" got \"%s\"", util.NO_URL_ERR.Error(), err.Error())
119+
}
120+
}
121+
122+
func TestWrongStatusCode(t *testing.T) {
123+
b := bytes.NewBufferString("")
124+
rootCmd.SetOut(b)
125+
rootCmd.SetArgs([]string{"wrongstatuscode", "--config", "./testdata/config.yaml"})
126+
err := rootCmd.Execute()
127+
if err.Error() != util.INVALID_RESP_CODE.Error() {
128+
t.Fatalf("expected \"%s\" got \"%s\"", util.INVALID_RESP_CODE.Error(), err.Error())
119129
}
120130
}

main.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package main
22

3-
import "github.com/nvima/httpcli/cmd"
3+
import (
4+
"os"
5+
6+
"github.com/nvima/httpcli/cmd"
7+
)
48

59
func main() {
6-
cmd.Execute()
10+
err := cmd.Execute()
11+
if err != nil {
12+
os.Exit(1)
13+
}
714
}
8-

util/errors.go

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,21 @@
11
package util
22

33
import (
4-
"fmt"
4+
"errors"
55
)
66

7-
type TplError struct {
8-
msg string
9-
err error
10-
}
11-
12-
func (te TplError) Err() error {
13-
return fmt.Errorf(te.msg)
14-
}
15-
16-
func (te TplError) Msg() string {
17-
return te.msg
18-
}
19-
20-
var NO_CONFIG_FILE_ERR = TplError{
21-
msg: "No config file found",
22-
}
23-
24-
var NO_FUNC_NAME_ERR = TplError{
25-
msg: "No function name provided",
26-
}
27-
var NO_FUNC_FOUND_ERR = TplError{
28-
msg: "Function not found in config",
29-
}
30-
31-
var INVALID_CONFIG_ERR = TplError{
32-
msg: "Invalid config file",
33-
}
34-
35-
var INVALID_RESP_CODE = TplError{
36-
msg: "Invalid response code",
37-
}
38-
var FAILED_TO_GET_DATA = TplError{
39-
msg: "Failed to get data",
40-
}
41-
var FAILED_TO_MAKE_HTTP_CALL = TplError{
42-
msg: "Failed to make http call",
43-
}
44-
var FAILED_TO_PARSE_JSON = TplError{
45-
msg: "Failed to parse JSON",
46-
}
47-
var FAILED_TO_PARSE_OUTPUT_FIELD = TplError{
48-
msg: "Failed to parse output field",
49-
}
50-
var NO_URL_ERR = TplError{
51-
msg: "No URL provided",
52-
}
7+
var NO_CONFIG_FILE_ERR = errors.New("No config file found")
8+
var NO_FUNC_NAME_ERR = errors.New("No function name provided")
9+
var NO_FUNC_FOUND_ERR = errors.New("Function not found in config")
10+
var INVALID_CONFIG_ERR = errors.New("Invalid config file")
11+
var INVALID_RESP_CODE = errors.New("Invalid response code")
12+
var FAILED_TO_GET_DATA = errors.New("Failed to get data")
13+
var FAILED_TO_MAKE_HTTP_CALL = errors.New("Failed to make http call")
14+
var FAILED_TO_PARSE_JSON_RESPONSE = errors.New("Failed to parse JSON Response")
15+
var FAILED_TO_PARSE_OUTPUT_FIELD = errors.New("Failed to parse output field")
16+
var NO_URL_ERR = errors.New("No URL provided")
17+
var MARSHAL_DATA_FAILED = errors.New("Failed to marshal data")
18+
var REPLACE_STDIN_FAILED = errors.New("Failed to replace stdin")
19+
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.")
20+
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.")
21+
var READ_RESPONSE_BODY_FAILED = errors.New("Failed to read the response body")

util/handleError.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package util
22

33
import (
4-
// "fmt"
4+
"fmt"
55

66
"github.com/spf13/cobra"
77
)
88

9-
func HandleError(cmd *cobra.Command, err error, tplError TplError) error {
10-
//TODO:: Add Error Logging with Stacktraces
11-
// fmt.Println(err)
12-
13-
// fmt.Fprintf(cmd.OutOrStdout(), tplError.msg)
14-
return err
9+
func HandleError(cmd *cobra.Command, err error, tplError error) error {
10+
debug, _ := cmd.Flags().GetBool("debug")
11+
if debug {
12+
fmt.Printf("Debug: %v\n", err)
13+
}
14+
return tplError
1515
}

0 commit comments

Comments
 (0)