fix(api): show clean RPC error messages
This commit is contained in:
parent
84776cc106
commit
0bfeb91d50
5 changed files with 55 additions and 2 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
|
@ -67,7 +68,7 @@ func convertGRPCError(err error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if st, ok := status.FromError(err); ok {
|
if st, ok := status.FromError(err); ok {
|
||||||
return connect.NewError(grpcCodeToConnectCode(st.Code()), err)
|
return connect.NewError(grpcCodeToConnectCode(st.Code()), errors.New(st.Message()))
|
||||||
}
|
}
|
||||||
return connect.NewError(connect.CodeInternal, err)
|
return connect.NewError(connect.CodeInternal, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
server/router/api/v1/connect_handler_test.go
Normal file
29
server/router/api/v1/connect_handler_test.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertGRPCErrorUsesStatusMessage(t *testing.T) {
|
||||||
|
const message = "invalid username: must start with a lowercase letter"
|
||||||
|
|
||||||
|
err := convertGRPCError(status.Error(codes.InvalidArgument, message))
|
||||||
|
connectErr, ok := err.(*connect.Error)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("convertGRPCError() returned %T, want *connect.Error", err)
|
||||||
|
}
|
||||||
|
if got := connectErr.Code(); got != connect.CodeInvalidArgument {
|
||||||
|
t.Fatalf("convertGRPCError() code = %v, want %v", got, connect.CodeInvalidArgument)
|
||||||
|
}
|
||||||
|
if got := connectErr.Message(); got != message {
|
||||||
|
t.Fatalf("convertGRPCError() message = %q, want %q", got, message)
|
||||||
|
}
|
||||||
|
if strings.Contains(connectErr.Message(), "rpc error:") {
|
||||||
|
t.Fatalf("convertGRPCError() message contains gRPC transport details: %q", connectErr.Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ function CreateUserDialog({ open, onOpenChange, user: initialUser, onSuccess }:
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
handleError(error, toast.error, {
|
handleError(error, toast.error, {
|
||||||
context: user ? "Update user" : "Create user",
|
context: isCreating ? "Create user" : "Update user",
|
||||||
onError: () => requestState.setError(),
|
onError: () => requestState.setError(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
|
import { ConnectError } from "@connectrpc/connect";
|
||||||
|
|
||||||
export function getErrorMessage(error: unknown, fallback = "Unknown error"): string {
|
export function getErrorMessage(error: unknown, fallback = "Unknown error"): string {
|
||||||
|
if (error instanceof ConnectError) {
|
||||||
|
return error.rawMessage || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
web/tests/error.test.ts
Normal file
17
web/tests/error.test.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Code, ConnectError } from "@connectrpc/connect";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { getErrorMessage } from "@/lib/error";
|
||||||
|
|
||||||
|
describe("getErrorMessage", () => {
|
||||||
|
it("returns a Connect error message without its status code", () => {
|
||||||
|
const error = new ConnectError("invalid username", Code.InvalidArgument);
|
||||||
|
|
||||||
|
expect(getErrorMessage(error)).toBe("invalid username");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the fallback for an empty Connect error message", () => {
|
||||||
|
const error = new ConnectError("", Code.InvalidArgument);
|
||||||
|
|
||||||
|
expect(getErrorMessage(error, "Request failed")).toBe("Request failed");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue