refactor(services): return *UpstreamError from decodeResponse

Replace the ad-hoc fmt.Errorf non-2xx branch with apierr.ParseGarage so
decodeResponse now returns a typed *apierr.UpstreamError on error.
Update two admin_test.go tests that string-matched the old error shape to
assert UpstreamError fields (HTTPStatus, Message) instead.
This commit is contained in:
Noooste
2026-04-20 00:09:01 +02:00
parent 691ed8db4d
commit c2e99afb8b
2 changed files with 23 additions and 9 deletions
+6 -5
View File
@@ -1,6 +1,7 @@
package services
import (
"Noooste/garage-ui/internal/apierr"
"Noooste/garage-ui/internal/config"
"Noooste/garage-ui/internal/models"
"Noooste/garage-ui/pkg/utils"
@@ -63,13 +64,14 @@ func (s *GarageAdminService) doRequest(ctx context.Context, method, path string,
return resp, nil
}
// decodeResponse decodes a JSON response into the target structure
// decodeResponse decodes a JSON response into the target structure. Non-2xx
// responses are converted to *apierr.UpstreamError (Source="garage") so the
// handler layer can map them to the correct API error code and HTTP status.
func decodeResponse(resp *azuretls.Response, target interface{}) error {
defer resp.RawBody.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(resp.RawBody)
return fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(bodyBytes))
if err := apierr.ParseGarage(resp); err != nil {
return err
}
if target != nil {
@@ -77,7 +79,6 @@ func decodeResponse(resp *azuretls.Response, target interface{}) error {
return fmt.Errorf("failed to decode response: %w", err)
}
}
return nil
}
+17 -4
View File
@@ -3,6 +3,7 @@ package services
import (
"context"
"encoding/json"
"errors"
"io"
"net"
"net/http"
@@ -11,6 +12,7 @@ import (
"testing"
"time"
"Noooste/garage-ui/internal/apierr"
"Noooste/garage-ui/internal/config"
"Noooste/garage-ui/internal/models"
)
@@ -103,8 +105,12 @@ func TestHealthCheck_Non2xxReturnsError(t *testing.T) {
if err == nil {
t.Fatal("expected error for 503, got nil")
}
if !strings.Contains(err.Error(), "503") {
t.Errorf("error should mention status code, got %v", err)
var ue *apierr.UpstreamError
if !errors.As(err, &ue) {
t.Fatalf("expected *apierr.UpstreamError, got %T (%v)", err, err)
}
if ue.HTTPStatus != http.StatusServiceUnavailable {
t.Errorf("HTTPStatus = %d, want %d", ue.HTTPStatus, http.StatusServiceUnavailable)
}
}
@@ -509,8 +515,15 @@ func TestDoRequest_Non2xxBodyEchoedInError(t *testing.T) {
if err == nil {
t.Fatal("expected error for 400, got nil")
}
if !strings.Contains(err.Error(), "400") || !strings.Contains(err.Error(), "bad request") {
t.Errorf("error %v should contain 400 and the response body", err)
var ue *apierr.UpstreamError
if !errors.As(err, &ue) {
t.Fatalf("expected *apierr.UpstreamError, got %T (%v)", err, err)
}
if ue.HTTPStatus != http.StatusBadRequest {
t.Errorf("HTTPStatus = %d, want %d", ue.HTTPStatus, http.StatusBadRequest)
}
if !strings.Contains(ue.Message, "bad request") {
t.Errorf("Message = %q, want it to contain the response body", ue.Message)
}
}