Skip to content

Commit ae5daed

Browse files
committed
feat: propagate failed http status codes
If a HTTP request fails (e.g. a resource is not found), propagate that as a failed (2) exit code.
1 parent ef02bde commit ae5daed

File tree

6 files changed

+65
-42
lines changed

6 files changed

+65
-42
lines changed

cmd/aepcli/main.go

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,22 @@ import (
1515
"github.com/spf13/cobra"
1616
)
1717

18+
const (
19+
CODE_OK = 0
20+
CODE_ERR = 1
21+
CODE_HTTP_ERROR_RESPONSE = 2
22+
)
23+
1824
func main() {
19-
err := aepcli(os.Args[1:])
25+
code, err := aepcli(os.Args[1:])
2026
if err != nil {
2127
fmt.Println(err)
2228
os.Exit(1)
2329
}
24-
os.Exit(0)
30+
os.Exit(code)
2531
}
2632

27-
func aepcli(args []string) error {
33+
func aepcli(args []string) (int, error) {
2834
var dryRun bool
2935
var logHTTP bool
3036
var logLevel string
@@ -49,7 +55,7 @@ func aepcli(args []string) error {
4955

5056
configFile, err := config.DefaultConfigFile()
5157
if err != nil {
52-
return fmt.Errorf("unable to get default config file: %w", err)
58+
return CODE_OK, fmt.Errorf("unable to get default config file: %w", err)
5359
}
5460

5561
rootCmd.Flags().SetInterspersed(false) // allow sub parsers to parse subsequent flags after the resource
@@ -63,30 +69,30 @@ func aepcli(args []string) error {
6369
rootCmd.SetArgs(args)
6470

6571
if err := rootCmd.Execute(); err != nil {
66-
return err
72+
return CODE_OK, err
6773
}
6874

6975
if configFileVar != "" {
7076
configFile = configFileVar
7177
}
7278

7379
if err := setLogLevel(logLevel); err != nil {
74-
return fmt.Errorf("unable to set log level: %w", err)
80+
return CODE_ERR, fmt.Errorf("unable to set log level: %w", err)
7581
}
7682

7783
c, err := config.ReadConfigFromFile(configFile)
7884
if err != nil {
79-
return fmt.Errorf("unable to read config: %v", err)
85+
return CODE_ERR, fmt.Errorf("unable to read config: %v", err)
8086
}
8187

8288
if fileAliasOrCore == "core" {
83-
return handleCoreCommand(additionalArgs, configFile)
89+
return CODE_OK, handleCoreCommand(additionalArgs, configFile)
8490
}
8591

8692
if api, ok := c.APIs[fileAliasOrCore]; ok {
8793
cd, err := config.ConfigDir()
8894
if err != nil {
89-
return fmt.Errorf("unable to get config directory: %w", err)
95+
return CODE_ERR, fmt.Errorf("unable to get config directory: %w", err)
9096
}
9197
if filepath.IsAbs(api.OpenAPIPath) || strings.HasPrefix(api.OpenAPIPath, "http") {
9298
fileAliasOrCore = api.OpenAPIPath
@@ -102,25 +108,33 @@ func aepcli(args []string) error {
102108

103109
oas, err := openapi.FetchOpenAPI(fileAliasOrCore)
104110
if err != nil {
105-
return fmt.Errorf("unable to fetch openapi: %w", err)
111+
return CODE_ERR, fmt.Errorf("unable to fetch openapi: %w", err)
106112
}
107113
api, err := api.GetAPI(oas, serverURL, pathPrefix)
108114
if err != nil {
109-
return fmt.Errorf("unable to get api: %w", err)
115+
return CODE_ERR, fmt.Errorf("unable to get api: %w", err)
110116
}
111117
headersMap, err := parseHeaders(headers)
112118
if err != nil {
113-
return fmt.Errorf("unable to parse headers: %w", err)
119+
return CODE_ERR, fmt.Errorf("unable to parse headers: %w", err)
114120
}
115121

116122
s = service.NewServiceCommand(api, headersMap, dryRun, logHTTP)
117123

118124
result, err := s.Execute(additionalArgs)
119-
fmt.Println(result)
125+
returnCode := CODE_OK
126+
output := ""
127+
if result != nil {
128+
output = result.Output
129+
if result.StatusCode != 0 && result.StatusCode/100 == 2 {
130+
returnCode = CODE_HTTP_ERROR_RESPONSE
131+
}
132+
}
133+
fmt.Println(output)
120134
if err != nil {
121-
return err
135+
return CODE_ERR, err
122136
}
123-
return nil
137+
return returnCode, nil
124138
}
125139

126140
func parseHeaders(headers []string) (map[string]string, error) {

cmd/aepcli/main_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestAepcli(t *testing.T) {
2222

2323
for _, tt := range tests {
2424
t.Run(tt.name, func(t *testing.T) {
25-
err := aepcli(tt.args)
25+
code, err := aepcli(tt.args)
2626

2727
if tt.wantErr {
2828
if err == nil {
@@ -38,6 +38,10 @@ func TestAepcli(t *testing.T) {
3838
if err != nil {
3939
t.Errorf("aepcli() unexpected error = %v", err)
4040
}
41+
42+
if code != 0 {
43+
t.Errorf("aepcli() unexpected exit code = %v, want 0", code)
44+
}
4145
})
4246
}
4347
}

internal/service/resource_definition_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func getTestAPI() *api.API {
5555
ServerURL: "https://api.example.com",
5656
Resources: map[string]*api.Resource{
5757
"project": &projectResource,
58-
"dataset": &api.Resource{
58+
"dataset": {
5959
Singular: "dataset",
6060
Plural: "datasets",
6161
Parents: []string{"project"},
@@ -68,13 +68,13 @@ func getTestAPI() *api.API {
6868
Delete: &api.DeleteMethod{},
6969
},
7070
},
71-
"user": &api.Resource{
71+
"user": {
7272
Singular: "user",
7373
Plural: "users",
7474
Parents: []string{},
7575
Schema: &openapi.Schema{},
7676
},
77-
"comment": &api.Resource{
77+
"comment": {
7878
Singular: "comment",
7979
Plural: "comments",
8080
Parents: []string{},

internal/service/service.go

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,41 +32,38 @@ func NewServiceCommand(api *api.API, headers map[string]string, dryRun bool, log
3232
}
3333
}
3434

35-
func (s *ServiceCommand) Execute(args []string) (string, error) {
35+
func (s *ServiceCommand) Execute(args []string) (*Result, error) {
3636
if len(args) == 0 || args[0] == "--help" {
37-
return s.PrintHelp(), nil
37+
return &Result{s.PrintHelp(), 0}, nil
3838
}
3939
resource := args[0]
4040
r, err := s.API.GetResource(resource)
4141
if err != nil {
42-
return "", fmt.Errorf("%v\n%v", err, s.PrintHelp())
42+
return nil, fmt.Errorf("%v\n%v", err, s.PrintHelp())
4343
}
4444
req, output, err := ExecuteResourceCommand(r, args[1:])
4545
if err != nil {
46-
return output, err
46+
return &Result{output, 0}, err
4747
}
4848
if req == nil {
49-
return output, nil
49+
return &Result{output, 0}, nil
5050
}
5151
url, err := url.Parse(fmt.Sprintf("%s/%s", s.API.ServerURL, req.URL.String()))
5252
if err != nil {
53-
return "", fmt.Errorf("unable to create url: %v", err)
53+
return nil, fmt.Errorf("unable to create url: %v", err)
5454
}
5555
req.URL = url
56-
reqOutput, err := s.doRequest(req)
56+
resp, err := s.doRequest(req)
5757
if err != nil {
58-
return "", fmt.Errorf("unable to execute request: %v", err)
58+
return nil, fmt.Errorf("unable to execute request: %v", err)
5959
}
60-
outputs := []string{}
61-
for _, o := range []string{output, reqOutput} {
62-
if o != "" {
63-
outputs = append(outputs, o)
64-
}
60+
if output != "" {
61+
resp.Output = output + "\n" + resp.Output
6562
}
66-
return strings.Join(outputs, "\n"), nil
63+
return resp, nil
6764
}
6865

69-
func (s *ServiceCommand) doRequest(r *http.Request) (string, error) {
66+
func (s *ServiceCommand) doRequest(r *http.Request) (*Result, error) {
7067
contentType := "application/json"
7168
if r.Method == http.MethodPatch {
7269
contentType = "application/merge-patch+json"
@@ -79,7 +76,7 @@ func (s *ServiceCommand) doRequest(r *http.Request) (string, error) {
7976
if r.Body != nil {
8077
b, err := io.ReadAll(r.Body)
8178
if err != nil {
82-
return "", fmt.Errorf("unable to read request body: %v", err)
79+
return nil, fmt.Errorf("unable to read request body: %v", err)
8380
}
8481
r.Body = io.NopCloser(bytes.NewBuffer(b))
8582
body = string(b)
@@ -91,23 +88,23 @@ func (s *ServiceCommand) doRequest(r *http.Request) (string, error) {
9188
}
9289
if s.DryRun {
9390
slog.Debug("Dry run: not making request")
94-
return "", nil
91+
return nil, nil
9592
}
9693
resp, err := s.Client.Do(r)
9794
if err != nil {
98-
return "", fmt.Errorf("unable to execute request: %v", err)
95+
return nil, fmt.Errorf("unable to execute request: %v", err)
9996
}
10097
defer resp.Body.Close()
10198
respBody, err := io.ReadAll(resp.Body)
10299
if err != nil {
103-
return "", fmt.Errorf("unable to read response body: %v", err)
100+
return nil, fmt.Errorf("unable to read response body: %v", err)
104101
}
105102
var prettyJSON bytes.Buffer
106103
err = json.Indent(&prettyJSON, respBody, "", " ")
107104
if err != nil {
108-
return "", fmt.Errorf("failed to format JSON: %w", err)
105+
return nil, fmt.Errorf("failed to format JSON: %w", err)
109106
}
110-
return prettyJSON.String(), nil
107+
return &Result{prettyJSON.String(), resp.StatusCode}, nil
111108
}
112109

113110
func (s *ServiceCommand) PrintHelp() string {

internal/service/service_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ func TestService_ExecuteCommand_ListResources(t *testing.T) {
5252
} else if !strings.Contains(err.Error(), tt.expected) {
5353
t.Errorf("ExecuteCommand() error = %v, expected it to contain %v", err, tt.expected)
5454
}
55-
} else if !strings.Contains(result, tt.expected) {
56-
t.Errorf("ExecuteCommand() = %q, expected it to contain %q", result, tt.expected)
55+
} else if !strings.Contains(result.Output, tt.expected) {
56+
t.Errorf("ExecuteCommand() = %q, expected it to contain %q", result.Output, tt.expected)
5757
}
5858
})
5959
}

internal/service/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package service
2+
3+
type Result struct {
4+
Output string
5+
// StatusCode should be -1 for undefined, or
6+
// the HTTP status code of the response.
7+
StatusCode int
8+
}

0 commit comments

Comments
 (0)