From 223aefc07437bccc1fcbc2432116a3f7d46a4da3 Mon Sep 17 00:00:00 2001 From: dinesh Date: Mon, 23 Dec 2024 02:48:37 +0530 Subject: [PATCH] chore: improved global error handling --- src/serviceLogs/errors.go | 72 +++++++++++++++++++++ src/serviceLogs/handler_formatByTemplate.go | 13 ++-- src/serviceLogs/handler_getLogs.go | 65 ++++++++++++++----- src/serviceLogs/handler_run.go | 15 ++++- 4 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 src/serviceLogs/errors.go diff --git a/src/serviceLogs/errors.go b/src/serviceLogs/errors.go new file mode 100644 index 00000000..40a4975b --- /dev/null +++ b/src/serviceLogs/errors.go @@ -0,0 +1,72 @@ +package serviceLogs + +import ( + "fmt" + "github.com/pkg/errors" +) + +// ErrInvalidRequest represents errors related to invalid API requests +type ErrInvalidRequest struct { + Op string + Message string + Err error +} + +func (e *ErrInvalidRequest) Error() string { + if e.Err != nil { + return fmt.Sprintf("%s: %s: %v", e.Op, e.Message, e.Err) + } + return fmt.Sprintf("%s: %s", e.Op, e.Message) +} + +func (e *ErrInvalidRequest) Unwrap() error { + return e.Err +} + +// ErrLogResponse represents errors related to log response parsing or validation +type ErrLogResponse struct { + StatusCode int + Message string + Err error +} + +func (e *ErrLogResponse) Error() string { + if e.Err != nil { + return fmt.Sprintf("log response error (status %d): %s: %v", e.StatusCode, e.Message, e.Err) + } + return fmt.Sprintf("log response error (status %d): %s", e.StatusCode, e.Message) +} + +func (e *ErrLogResponse) Unwrap() error { + return e.Err +} + +// NewInvalidRequestError creates a new ErrInvalidRequest +func NewInvalidRequestError(op string, message string, err error) error { + return &ErrInvalidRequest{ + Op: op, + Message: message, + Err: err, + } +} + +// NewLogResponseError creates a new ErrLogResponse +func NewLogResponseError(statusCode int, message string, err error) error { + return &ErrLogResponse{ + StatusCode: statusCode, + Message: message, + Err: err, + } +} + +// IsInvalidRequestError checks if the error is an ErrInvalidRequest +func IsInvalidRequestError(err error) bool { + var target *ErrInvalidRequest + return errors.As(err, &target) +} + +// IsLogResponseError checks if the error is an ErrLogResponse +func IsLogResponseError(err error) bool { + var target *ErrLogResponse + return errors.As(err, &target) +} diff --git a/src/serviceLogs/handler_formatByTemplate.go b/src/serviceLogs/handler_formatByTemplate.go index 8ccfd762..817ce682 100644 --- a/src/serviceLogs/handler_formatByTemplate.go +++ b/src/serviceLogs/handler_formatByTemplate.go @@ -14,6 +14,10 @@ import ( ) func getFullWithTemplate(logData []Data, formatTemplate string) error { + if len(logData) == 0 { + return NewInvalidRequestError("getFullWithTemplate", "no log data available", nil) + } + ft, err := fixTemplate(formatTemplate) if err != nil { return err @@ -31,12 +35,13 @@ func formatDataByTemplate(data Data, formatTemplate string) error { var b bytes.Buffer t, err := template.New("").Parse(formatTemplate) if err != nil { - return err + return NewInvalidRequestError("formatDataByTemplate", "failed to parse template", err) } - err = t.Execute(&b, data) - if err != nil { - return err + + if err = t.Execute(&b, data); err != nil { + return NewInvalidRequestError("formatDataByTemplate", "failed to execute template", err) } + fmt.Println(b.String()) return nil } diff --git a/src/serviceLogs/handler_getLogs.go b/src/serviceLogs/handler_getLogs.go index 9eca8e97..7f8f7cc8 100644 --- a/src/serviceLogs/handler_getLogs.go +++ b/src/serviceLogs/handler_getLogs.go @@ -3,6 +3,8 @@ package serviceLogs import ( "context" "encoding/json" + "errors" + "fmt" "io" "net/http" "time" @@ -33,45 +35,78 @@ type Data struct { Message string `json:"message"` } +var ( + ErrInvalidRequest = errors.New("invalid request") + ErrLogResponse = errors.New("log response error") + ErrInvalidResponse = errors.New("invalid response") +) + +type InvalidRequestError struct { + FuncName string + Msg string + Err error +} + +func (e *InvalidRequestError) Error() string { + return fmt.Sprintf("%s: %s: %v", e.FuncName, e.Msg, e.Err) +} + +func NewInvalidRequestError(funcName, msg string, err error) error { + return &InvalidRequestError{FuncName: funcName, Msg: msg, Err: err} +} + +type LogResponseError struct { + StatusCode int + Msg string + Err error +} + +func (e *LogResponseError) Error() string { + return fmt.Sprintf("status code: %d: %s: %v", e.StatusCode, e.Msg, e.Err) +} + +func NewLogResponseError(statusCode int, msg string, err error) error { + return &LogResponseError{StatusCode: statusCode, Msg: msg, Err: err} +} + func getLogs(ctx context.Context, method, url, format, formatTemplate, mode string) error { c := http.Client{Timeout: time.Duration(1) * time.Minute} req, err := http.NewRequest(method, url, nil) if err != nil { - return err + return NewInvalidRequestError("getLogs", "failed to create request", err) } req = req.WithContext(ctx) req.Header.Add("Content-Type", "application/json") resp, err := c.Do(req) if err != nil { - return err + return NewInvalidRequestError("getLogs", "failed to execute request", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) - if err != nil { - return err + return NewLogResponseError(resp.StatusCode, "failed to read response body", err) } - jsonData, err := parseResponse(body) - if err != nil { - return err + if resp.StatusCode != http.StatusOK { + return NewLogResponseError(resp.StatusCode, fmt.Sprintf("unexpected status code: %d", resp.StatusCode), nil) } - err = parseResponseByFormat(jsonData, format, formatTemplate, mode) + + jsonData, err := parseResponse(body) if err != nil { - return err + return NewLogResponseError(resp.StatusCode, "failed to parse response", err) } - return nil + + return parseResponseByFormat(jsonData, format, formatTemplate, mode) } func parseResponse(body []byte) (Response, error) { - var jsonData Response - err := json.Unmarshal(body, &jsonData) - if err != nil || len(jsonData.Items) == 0 { - return Response{}, err + var response Response + if err := json.Unmarshal(body, &response); err != nil { + return Response{}, NewLogResponseError(0, "failed to unmarshal response", err) } - return jsonData, nil + return response, nil } diff --git a/src/serviceLogs/handler_run.go b/src/serviceLogs/handler_run.go index d91fe828..536c28bb 100644 --- a/src/serviceLogs/handler_run.go +++ b/src/serviceLogs/handler_run.go @@ -7,12 +7,21 @@ import ( func (h *Handler) Run(ctx context.Context, config RunConfig) error { inputs, err := h.checkInputValues(config) if err != nil { - return err + return NewInvalidRequestError("Run", "invalid input values", err) + } + + if config.Container.ID == "" { + return NewInvalidRequestError("Run", "container ID is required", nil) } - // TODO - janhajek check empty containerID if err = h.printLogs(ctx, inputs, config.Project.ID, config.ServiceId, config.Container.ID); err != nil { - return err + if IsInvalidRequestError(err) { + return err + } + if IsLogResponseError(err) { + return err + } + return NewLogResponseError(0, "failed to print logs", err) } return nil