refactor: derive api error codes from contract (WealthLedger/backend#117)
This commit is contained in:
@@ -59,8 +59,7 @@ type AccountPageViewModel
|
||||
| { state: "loading" };
|
||||
|
||||
function isPortfolioAccessFailure(code?: string) {
|
||||
return isApiErrorCode(code, ApiErrorCode.Forbidden)
|
||||
|| isApiErrorCode(code, ApiErrorCode.InvalidTbankToken)
|
||||
return isApiErrorCode(code, ApiErrorCode.InvalidTbankToken)
|
||||
|| isApiErrorCode(code, ApiErrorCode.NotFound)
|
||||
|| isApiErrorCode(code, ApiErrorCode.TbankNotConnected);
|
||||
}
|
||||
|
||||
@@ -285,7 +285,11 @@ describe("apiClient transient read recovery", () => {
|
||||
http.get("*/api/v1/brokerage-accounts", () => {
|
||||
requests += 1;
|
||||
|
||||
return HttpResponse.json({ code: "FORBIDDEN" }, { status });
|
||||
// Any non-transient code; status alone must prevent the retry.
|
||||
return HttpResponse.json(
|
||||
{ code: "INTERNAL_SERVER_ERROR", status },
|
||||
{ status },
|
||||
);
|
||||
}),
|
||||
);
|
||||
configureAuthClient({
|
||||
|
||||
@@ -166,7 +166,6 @@ function isNetworkFailure(error: unknown): boolean {
|
||||
|
||||
function isPermanentCode(code?: string): boolean {
|
||||
return [
|
||||
ApiErrorCode.Forbidden,
|
||||
ApiErrorCode.InvalidTbankToken,
|
||||
ApiErrorCode.NotFound,
|
||||
ApiErrorCode.TbankNotConnected,
|
||||
|
||||
Vendored
+5
-2
@@ -731,8 +731,11 @@ export interface components {
|
||||
ProblemDetailsDto: {
|
||||
/** @example 401 */
|
||||
status: number;
|
||||
/** @example UNAUTHORIZED */
|
||||
code: string;
|
||||
/**
|
||||
* @example UNAUTHORIZED
|
||||
* @enum {string}
|
||||
*/
|
||||
code: "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_FOUND" | "UNAUTHORIZED" | "UNSUPPORTED_MEDIA_TYPE" | "VALIDATION_ERROR" | "EMAIL_ALREADY_REGISTERED" | "INVALID_CREDENTIALS" | "INVALID_OPERATIONS_CURSOR" | "BOND_HISTORY_START_UNAVAILABLE" | "BOND_TICKER_AMBIGUOUS" | "STOCK_HISTORY_START_UNAVAILABLE" | "STOCK_TICKER_AMBIGUOUS" | "INVALID_TBANK_TOKEN" | "TBANK_NOT_CONNECTED" | "TBANK_UNAVAILABLE" | "BANK_CARD_ARCHIVED" | "BANK_DEPENDENT_REFUNDS" | "BANK_DUPLICATE_CATEGORY" | "BANK_IMMUTABLE_OPERATION" | "BANK_INTENT_CONFLICT" | "BANK_INVALID_DATE" | "BANK_INVALID_INPUT" | "BANK_INVALID_MONEY" | "BANK_INVALID_REFUND" | "BANK_NOT_FOUND" | "BANK_OPENING_DATE_CONFLICT" | "BANK_OPERATION_DELETED" | "BANK_REFUND_LIMIT" | "BANK_VERSION_CONFLICT" | "BANK_WRITE_CONFLICT" | "MOEX_UNAVAILABLE" | "NCR_UNAVAILABLE" | "INVALID_EVENTS_PERIOD" | "INVALID_INCOME_PERIOD";
|
||||
/**
|
||||
* @example [
|
||||
* {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { components } from "./generated/schema";
|
||||
|
||||
enum ApiErrorCode {
|
||||
BankDependentRefunds = "BANK_DEPENDENT_REFUNDS",
|
||||
BankIntentConflict = "BANK_INTENT_CONFLICT",
|
||||
@@ -6,7 +8,6 @@ enum ApiErrorCode {
|
||||
BankVersionConflict = "BANK_VERSION_CONFLICT",
|
||||
BankWriteConflict = "BANK_WRITE_CONFLICT",
|
||||
BondTickerAmbiguous = "BOND_TICKER_AMBIGUOUS",
|
||||
Forbidden = "FORBIDDEN",
|
||||
InvalidOperationsCursor = "INVALID_OPERATIONS_CURSOR",
|
||||
InvalidTbankToken = "INVALID_TBANK_TOKEN",
|
||||
NotFound = "NOT_FOUND",
|
||||
@@ -16,6 +17,16 @@ enum ApiErrorCode {
|
||||
Unauthorized = "UNAUTHORIZED",
|
||||
}
|
||||
|
||||
/**
|
||||
Compile-time drift guard: collapses to `never` when any enum member is absent
|
||||
from the generated ProblemDetails contract, so every call site fails to build.
|
||||
*/
|
||||
type ContractApiErrorCode
|
||||
= Exclude<
|
||||
`${ApiErrorCode}`,
|
||||
components["schemas"]["ProblemDetailsDto"]["code"]
|
||||
> extends never ? ApiErrorCode : never;
|
||||
|
||||
function apiErrorCode(error: unknown): string | undefined {
|
||||
return typeof error === "object" && error !== null && "code" in error
|
||||
? String(error.code)
|
||||
@@ -23,7 +34,10 @@ function apiErrorCode(error: unknown): string | undefined {
|
||||
}
|
||||
|
||||
// Incoming codes are an open wire string; unknown values retain their fallback.
|
||||
function isApiErrorCode(value: unknown, expected: ApiErrorCode): boolean {
|
||||
function isApiErrorCode(
|
||||
value: unknown,
|
||||
expected: ContractApiErrorCode,
|
||||
): boolean {
|
||||
return value === expected;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ function invalidateResourceAccess(
|
||||
const errorCode = apiErrorCode(error);
|
||||
if (
|
||||
[
|
||||
ApiErrorCode.Forbidden,
|
||||
ApiErrorCode.InvalidTbankToken,
|
||||
ApiErrorCode.NotFound,
|
||||
ApiErrorCode.TbankNotConnected,
|
||||
|
||||
@@ -5204,7 +5204,44 @@
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"example": "UNAUTHORIZED"
|
||||
"example": "UNAUTHORIZED",
|
||||
"enum": [
|
||||
"BAD_REQUEST",
|
||||
"INTERNAL_SERVER_ERROR",
|
||||
"NOT_FOUND",
|
||||
"UNAUTHORIZED",
|
||||
"UNSUPPORTED_MEDIA_TYPE",
|
||||
"VALIDATION_ERROR",
|
||||
"EMAIL_ALREADY_REGISTERED",
|
||||
"INVALID_CREDENTIALS",
|
||||
"INVALID_OPERATIONS_CURSOR",
|
||||
"BOND_HISTORY_START_UNAVAILABLE",
|
||||
"BOND_TICKER_AMBIGUOUS",
|
||||
"STOCK_HISTORY_START_UNAVAILABLE",
|
||||
"STOCK_TICKER_AMBIGUOUS",
|
||||
"INVALID_TBANK_TOKEN",
|
||||
"TBANK_NOT_CONNECTED",
|
||||
"TBANK_UNAVAILABLE",
|
||||
"BANK_CARD_ARCHIVED",
|
||||
"BANK_DEPENDENT_REFUNDS",
|
||||
"BANK_DUPLICATE_CATEGORY",
|
||||
"BANK_IMMUTABLE_OPERATION",
|
||||
"BANK_INTENT_CONFLICT",
|
||||
"BANK_INVALID_DATE",
|
||||
"BANK_INVALID_INPUT",
|
||||
"BANK_INVALID_MONEY",
|
||||
"BANK_INVALID_REFUND",
|
||||
"BANK_NOT_FOUND",
|
||||
"BANK_OPENING_DATE_CONFLICT",
|
||||
"BANK_OPERATION_DELETED",
|
||||
"BANK_REFUND_LIMIT",
|
||||
"BANK_VERSION_CONFLICT",
|
||||
"BANK_WRITE_CONFLICT",
|
||||
"MOEX_UNAVAILABLE",
|
||||
"NCR_UNAVAILABLE",
|
||||
"INVALID_EVENTS_PERIOD",
|
||||
"INVALID_INCOME_PERIOD"
|
||||
]
|
||||
},
|
||||
"errors": {
|
||||
"type": "array",
|
||||
|
||||
Reference in New Issue
Block a user