diff --git a/cmd/aepcli/main.go b/cmd/aepcli/main.go index c8b9b67..00ee83a 100644 --- a/cmd/aepcli/main.go +++ b/cmd/aepcli/main.go @@ -15,16 +15,22 @@ import ( "github.com/spf13/cobra" ) +const ( + CODE_OK = 0 + CODE_ERR = 1 + CODE_HTTP_ERROR_RESPONSE = 2 +) + func main() { - err := aepcli(os.Args[1:]) + code, err := aepcli(os.Args[1:]) if err != nil { fmt.Println(err) os.Exit(1) } - os.Exit(0) + os.Exit(code) } -func aepcli(args []string) error { +func aepcli(args []string) (int, error) { var dryRun bool var logHTTP bool var logLevel string @@ -49,7 +55,7 @@ func aepcli(args []string) error { configFile, err := config.DefaultConfigFile() if err != nil { - return fmt.Errorf("unable to get default config file: %w", err) + return CODE_OK, fmt.Errorf("unable to get default config file: %w", err) } rootCmd.Flags().SetInterspersed(false) // allow sub parsers to parse subsequent flags after the resource @@ -63,7 +69,7 @@ func aepcli(args []string) error { rootCmd.SetArgs(args) if err := rootCmd.Execute(); err != nil { - return err + return CODE_OK, err } if configFileVar != "" { @@ -71,22 +77,22 @@ func aepcli(args []string) error { } if err := setLogLevel(logLevel); err != nil { - return fmt.Errorf("unable to set log level: %w", err) + return CODE_ERR, fmt.Errorf("unable to set log level: %w", err) } c, err := config.ReadConfigFromFile(configFile) if err != nil { - return fmt.Errorf("unable to read config: %v", err) + return CODE_ERR, fmt.Errorf("unable to read config: %v", err) } if fileAliasOrCore == "core" { - return handleCoreCommand(additionalArgs, configFile) + return CODE_OK, handleCoreCommand(additionalArgs, configFile) } if api, ok := c.APIs[fileAliasOrCore]; ok { cd, err := config.ConfigDir() if err != nil { - return fmt.Errorf("unable to get config directory: %w", err) + return CODE_ERR, fmt.Errorf("unable to get config directory: %w", err) } if filepath.IsAbs(api.OpenAPIPath) || strings.HasPrefix(api.OpenAPIPath, "http") { fileAliasOrCore = api.OpenAPIPath @@ -102,25 +108,33 @@ func aepcli(args []string) error { oas, err := openapi.FetchOpenAPI(fileAliasOrCore) if err != nil { - return fmt.Errorf("unable to fetch openapi: %w", err) + return CODE_ERR, fmt.Errorf("unable to fetch openapi: %w", err) } api, err := api.GetAPI(oas, serverURL, pathPrefix) if err != nil { - return fmt.Errorf("unable to get api: %w", err) + return CODE_ERR, fmt.Errorf("unable to get api: %w", err) } headersMap, err := parseHeaders(headers) if err != nil { - return fmt.Errorf("unable to parse headers: %w", err) + return CODE_ERR, fmt.Errorf("unable to parse headers: %w", err) } s = service.NewServiceCommand(api, headersMap, dryRun, logHTTP) result, err := s.Execute(additionalArgs) - fmt.Println(result) + returnCode := CODE_OK + output := "" + if result != nil { + output = result.Output + if result.StatusCode != 0 && result.StatusCode/100 == 2 { + returnCode = CODE_HTTP_ERROR_RESPONSE + } + } + fmt.Println(output) if err != nil { - return err + return CODE_ERR, err } - return nil + return returnCode, nil } func parseHeaders(headers []string) (map[string]string, error) { diff --git a/cmd/aepcli/main_test.go b/cmd/aepcli/main_test.go index b006a81..332b122 100644 --- a/cmd/aepcli/main_test.go +++ b/cmd/aepcli/main_test.go @@ -22,7 +22,7 @@ func TestAepcli(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := aepcli(tt.args) + code, err := aepcli(tt.args) if tt.wantErr { if err == nil { @@ -38,6 +38,10 @@ func TestAepcli(t *testing.T) { if err != nil { t.Errorf("aepcli() unexpected error = %v", err) } + + if code != 0 { + t.Errorf("aepcli() unexpected exit code = %v, want 0", code) + } }) } } diff --git a/internal/service/resource_definition_test.go b/internal/service/resource_definition_test.go index b41ddd6..7ebdb7b 100644 --- a/internal/service/resource_definition_test.go +++ b/internal/service/resource_definition_test.go @@ -55,7 +55,7 @@ func getTestAPI() *api.API { ServerURL: "https://api.example.com", Resources: map[string]*api.Resource{ "project": &projectResource, - "dataset": &api.Resource{ + "dataset": { Singular: "dataset", Plural: "datasets", Parents: []string{"project"}, @@ -68,13 +68,13 @@ func getTestAPI() *api.API { Delete: &api.DeleteMethod{}, }, }, - "user": &api.Resource{ + "user": { Singular: "user", Plural: "users", Parents: []string{}, Schema: &openapi.Schema{}, }, - "comment": &api.Resource{ + "comment": { Singular: "comment", Plural: "comments", Parents: []string{}, diff --git a/internal/service/service.go b/internal/service/service.go index 7c27eeb..62866ca 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -32,41 +32,38 @@ func NewServiceCommand(api *api.API, headers map[string]string, dryRun bool, log } } -func (s *ServiceCommand) Execute(args []string) (string, error) { +func (s *ServiceCommand) Execute(args []string) (*Result, error) { if len(args) == 0 || args[0] == "--help" { - return s.PrintHelp(), nil + return &Result{s.PrintHelp(), 0}, nil } resource := args[0] r, err := s.API.GetResource(resource) if err != nil { - return "", fmt.Errorf("%v\n%v", err, s.PrintHelp()) + return nil, fmt.Errorf("%v\n%v", err, s.PrintHelp()) } req, output, err := ExecuteResourceCommand(r, args[1:]) if err != nil { - return output, err + return &Result{output, 0}, err } if req == nil { - return output, nil + return &Result{output, 0}, nil } url, err := url.Parse(fmt.Sprintf("%s/%s", s.API.ServerURL, req.URL.String())) if err != nil { - return "", fmt.Errorf("unable to create url: %v", err) + return nil, fmt.Errorf("unable to create url: %v", err) } req.URL = url - reqOutput, err := s.doRequest(req) + resp, err := s.doRequest(req) if err != nil { - return "", fmt.Errorf("unable to execute request: %v", err) + return nil, fmt.Errorf("unable to execute request: %v", err) } - outputs := []string{} - for _, o := range []string{output, reqOutput} { - if o != "" { - outputs = append(outputs, o) - } + if output != "" { + resp.Output = output + "\n" + resp.Output } - return strings.Join(outputs, "\n"), nil + return resp, nil } -func (s *ServiceCommand) doRequest(r *http.Request) (string, error) { +func (s *ServiceCommand) doRequest(r *http.Request) (*Result, error) { contentType := "application/json" if r.Method == http.MethodPatch { contentType = "application/merge-patch+json" @@ -79,7 +76,7 @@ func (s *ServiceCommand) doRequest(r *http.Request) (string, error) { if r.Body != nil { b, err := io.ReadAll(r.Body) if err != nil { - return "", fmt.Errorf("unable to read request body: %v", err) + return nil, fmt.Errorf("unable to read request body: %v", err) } r.Body = io.NopCloser(bytes.NewBuffer(b)) body = string(b) @@ -91,23 +88,23 @@ func (s *ServiceCommand) doRequest(r *http.Request) (string, error) { } if s.DryRun { slog.Debug("Dry run: not making request") - return "", nil + return nil, nil } resp, err := s.Client.Do(r) if err != nil { - return "", fmt.Errorf("unable to execute request: %v", err) + return nil, fmt.Errorf("unable to execute request: %v", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { - return "", fmt.Errorf("unable to read response body: %v", err) + return nil, fmt.Errorf("unable to read response body: %v", err) } var prettyJSON bytes.Buffer err = json.Indent(&prettyJSON, respBody, "", " ") if err != nil { - return "", fmt.Errorf("failed to format JSON: %w", err) + return nil, fmt.Errorf("failed to format JSON: %w", err) } - return prettyJSON.String(), nil + return &Result{prettyJSON.String(), resp.StatusCode}, nil } func (s *ServiceCommand) PrintHelp() string { diff --git a/internal/service/service_test.go b/internal/service/service_test.go index 9b98d33..5a2fd9e 100644 --- a/internal/service/service_test.go +++ b/internal/service/service_test.go @@ -52,8 +52,8 @@ func TestService_ExecuteCommand_ListResources(t *testing.T) { } else if !strings.Contains(err.Error(), tt.expected) { t.Errorf("ExecuteCommand() error = %v, expected it to contain %v", err, tt.expected) } - } else if !strings.Contains(result, tt.expected) { - t.Errorf("ExecuteCommand() = %q, expected it to contain %q", result, tt.expected) + } else if !strings.Contains(result.Output, tt.expected) { + t.Errorf("ExecuteCommand() = %q, expected it to contain %q", result.Output, tt.expected) } }) } diff --git a/internal/service/types.go b/internal/service/types.go new file mode 100644 index 0000000..2c2993c --- /dev/null +++ b/internal/service/types.go @@ -0,0 +1,8 @@ +package service + +type Result struct { + Output string + // StatusCode should be 0 for undefined, or + // the HTTP status code of the response. + StatusCode int +}