Skip to content

Commit

Permalink
Merge pull request #6 from nvima/refactor-error-handling-status-code
Browse files Browse the repository at this point in the history
Refactor CLI tool: Improved error handling, added status code check
  • Loading branch information
nvima authored Apr 22, 2023
2 parents 794d756 + d248312 commit dd17259
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 41 deletions.
1 change: 1 addition & 0 deletions cmd/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gitdiff:
- role: "user"
content: "Hello"
output: "choices[0].message.content"
statuscode: 200
env:
- "TEST_API_KEY"
- "TEST_SERVER_URL"
Expand Down
88 changes: 58 additions & 30 deletions cmd/tpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,51 +16,61 @@ import (
)

type FunctionConfig struct {
Header []string `mapstructure:"header"`
Data map[string]interface{} `mapstructure:"data"`
Env []string `mapstructure:"env"`
Url string `mapstructure:"url"`
Output string `mapstructure:"output"`
Header []string `mapstructure:"header"`
Data map[string]interface{} `mapstructure:"data"`
Env []string `mapstructure:"env"`
Url string `mapstructure:"url"`
Output string `mapstructure:"output"`
StatusCode int `mapstructure:"statuscode"`
}

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)
fmt.Fprintf(cmd.OutOrStdout(), output)
}

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

if len(config) == 0 {
panic("No config found")
util.HandleError(cmd, util.NO_FUNC_NAME_ERR.Err(), util.NO_CONFIG_FILE_ERR)
}

if len(args) == 0 {
panic("No function name provided")
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 {
panic("Failed to decode config: " + err.Error())
util.HandleError(cmd, err, util.INVALID_CONFIG_ERR)
}

fc, ok := appConfig.Functions[args[0]]
if !ok {
panic("No config found for function: " + args[0])
util.HandleError(cmd, util.NO_FUNC_FOUND_ERR.Err(), util.NO_FUNC_FOUND_ERR)
}

fmt.Fprintf(cmd.OutOrStdout(), fc.handleFunc())
}

func (fc *FunctionConfig) handleFunc() string {
jsonData := fc.getJSONData()
return fc
}

req, err := http.NewRequest("POST", fc.replaceEnvVariables(fc.Url), bytes.NewBuffer(jsonData))
func (fc *FunctionConfig) makeHttpCall(jsonData []byte, cmd *cobra.Command) ([]byte, error) {
url := fc.replaceEnvVariables(fc.Url)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
panic("Failed to create request: " + err.Error())
return nil, err
}

for _, header := range fc.Header {
header = fc.replaceEnvVariables(header)

headerParts := strings.SplitN(header, ":", 2)
if len(headerParts) == 2 {
req.Header.Set(strings.TrimSpace(headerParts[0]), strings.TrimSpace(headerParts[1]))
Expand All @@ -70,42 +80,59 @@ func (fc *FunctionConfig) handleFunc() string {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic("Failed to send request: " + err.Error())
return nil, err
}
// Read the response body

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic("Failed to read response body: " + err.Error())
return nil, err
}
defer resp.Body.Close()

// Check if the request was successful
if resp.StatusCode != http.StatusOK {
panic("Request failed, Status: " + resp.Status + ", Body: " + string(body))
// 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 {
jsonData, err := fc.getJSONData()
if err != nil {
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)
}

// Parse the JSON response
responseData, err := util.ParseJSONResponse(body)
if err != nil {
panic(err)
util.HandleError(cmd, err, util.FAILED_TO_PARSE_JSON)
}

// Extract the desired output from the JSON response
return util.GetOutputField(responseData, fc.Output)
output, err := util.GetOutputField(responseData, fc.Output)
if err != nil {
util.HandleError(cmd, err, util.FAILED_TO_PARSE_OUTPUT_FIELD)
}

return output
}

func (fc *FunctionConfig) getJSONData() []byte {
func (fc *FunctionConfig) getJSONData() ([]byte, error) {
jsonData, err := json.Marshal(fc.Data)
if err != nil {
panic(err)
return nil, err
}

jsonData, err = util.ReplaceStdIn(jsonData)
if err != nil {
panic(err)
return nil, err
}

return jsonData
return jsonData, nil
}

func (fc *FunctionConfig) replaceEnvVariables(value string) string {
Expand All @@ -114,5 +141,6 @@ func (fc *FunctionConfig) replaceEnvVariables(value string) string {
placeholder := fmt.Sprintf("${%s}", envVar)
value = strings.Replace(value, placeholder, envValue, -1)
}

return value
}
63 changes: 63 additions & 0 deletions util/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package util

import (
"fmt"
)

type TplError struct {
msg string
err error
exitCode int
}

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

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,
}
10 changes: 6 additions & 4 deletions util/handleError.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package util
import (
"fmt"
"os"

"github.com/spf13/cobra"
)

func HandleError(err error, msg string) {
// fmt.Println(err)
fmt.Println(msg)
os.Exit(1)
func HandleError(cmd *cobra.Command, err error, tplError TplError) {
// fmt.Println(err)
fmt.Fprintf(cmd.OutOrStdout(), tplError.msg)
os.Exit(tplError.exitCode)
}
16 changes: 9 additions & 7 deletions util/replaceStdIn.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func ParseJSONResponse(jsonData []byte) (map[string]interface{}, error) {
return data, nil
}

func GetOutputField(data interface{}, fieldPath string) string {
func GetOutputField(data interface{}, fieldPath string) (string, error) {
keys := strings.Split(fieldPath, ".")

var result interface{} = data
Expand All @@ -67,14 +67,14 @@ func GetOutputField(data interface{}, fieldPath string) string {
index := key[strings.Index(key, "[")+1 : strings.Index(key, "]")]
m, ok := result.(map[string]interface{})[innerKey].([]interface{})
if !ok {
panic("invalid output path")
return "", fmt.Errorf("invalid output path")
}
intVar, _ := strconv.Atoi(index)
result = m[intVar]
} else {
m, ok := result.(map[string]interface{})[key]
if !ok {
panic("invalid output path")
return "", fmt.Errorf("invalid output path")
}
result = m
}
Expand All @@ -83,12 +83,14 @@ func GetOutputField(data interface{}, fieldPath string) string {
if _, ok := result.(map[string]interface{}); ok {
jsonResult, err := json.Marshal(result)
if err != nil {
panic(err)
return "", err
}
return string(jsonResult)
return string(jsonResult), nil
}

if _, ok := result.(string); ok {
return result.(string)
return result.(string), nil
}
panic("invalid output path")

return "", fmt.Errorf("invalid output path")
}

0 comments on commit dd17259

Please sign in to comment.