fix: show the reason the server gave for a failed action
Every failure in the management screens printed a guess. Deleting a working copy said "it may be published, or something may reference it, or someone may have edited it first" — three maybes, while the server had answered with exactly one: "이 기록을 참조하는 곳이 있어 삭제할 수 없습니다". A version conflict read as "in use" because the same sentence covered both, and an author watching some deletions succeed and others fail had no way to tell them apart. The gateway now carries the server's client-safe message and the screens show it. The canned sentences remain only as a fallback for a failure that never reached the server. The topic status label said "사용 중" for every active topic, including one created seconds earlier that nothing references. Next to a refusal about records that use a topic, the two read as the same statement. It says "활성" now, which is what the status means. Publishing also stops demanding a finished document — the mock validator moves with the real one, so what an author sees against fixtures matches production.
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
TopicEdit,
|
||||
} from "../../contracts/management/contract.ts";
|
||||
import type { ManagementGateway } from "../../application/ports/management-gateway.ts";
|
||||
import { ManagementGatewayError } from "../../application/ports/management-gateway-error.ts";
|
||||
import type { StudioOperationExecutor } from "./http-studio-gateway.ts";
|
||||
|
||||
export type { ManagementGateway };
|
||||
@@ -23,17 +24,10 @@ const ROUTE_ID = "TECH_LOG_STUDIO";
|
||||
* Studio 쪽 상태 처리를 그대로 쓴다. 여기서 Result 로 감싸면 이 표면만 다른 규약이 된다.
|
||||
*/
|
||||
|
||||
export class ManagementGatewayError extends Error {
|
||||
readonly operationId: string;
|
||||
readonly code: string;
|
||||
|
||||
constructor(operationId: string, code: string) {
|
||||
super(`${operationId}: ${code}`);
|
||||
this.name = "ManagementGatewayError";
|
||||
this.operationId = operationId;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
export {
|
||||
ManagementGatewayError,
|
||||
managementFailureMessage,
|
||||
} from "../../application/ports/management-gateway-error.ts";
|
||||
|
||||
export function createHttpManagementGateway(
|
||||
deps: Readonly<{ operations: StudioOperationExecutor }>,
|
||||
@@ -46,7 +40,11 @@ export function createHttpManagementGateway(
|
||||
// the code under `error` — reading `problem.code` found nothing and every
|
||||
// failure surfaced as the literal "PROBLEM", matching no i18n key.
|
||||
const body = outcome.problem as
|
||||
| Readonly<{ code?: unknown; error?: Readonly<{ code?: unknown }> }>
|
||||
| Readonly<{
|
||||
code?: unknown;
|
||||
detail?: unknown;
|
||||
error?: Readonly<{ code?: unknown; message?: unknown }>;
|
||||
}>
|
||||
| null;
|
||||
const code =
|
||||
typeof body?.code === "string"
|
||||
@@ -54,9 +52,15 @@ export function createHttpManagementGateway(
|
||||
: typeof body?.error?.code === "string"
|
||||
? body.error.code
|
||||
: "PROBLEM";
|
||||
throw new ManagementGatewayError(operationId, code);
|
||||
const detail =
|
||||
typeof body?.error?.message === "string"
|
||||
? body.error.message
|
||||
: typeof body?.detail === "string"
|
||||
? body.detail
|
||||
: "";
|
||||
throw new ManagementGatewayError(operationId, code, detail);
|
||||
}
|
||||
throw new ManagementGatewayError(operationId, outcome.kind);
|
||||
throw new ManagementGatewayError(operationId, outcome.kind, "");
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
|
||||
Reference in New Issue
Block a user