parent
164e6fb9db
commit
37a5e008f5
@ -0,0 +1,82 @@
|
||||
import type { StandardSchemaV1 } from '$libs/standard-schema';
|
||||
import {
|
||||
HTTP_DIAGNOSTIC_EVENTS,
|
||||
HTTP_RESULT_KIND_NETWORK,
|
||||
HTTP_RESULT_KIND_VALIDATION,
|
||||
httpStatusErrorMessage
|
||||
} from './consts.ts';
|
||||
import { emitHttpDiagnostic, type HttpDiagnostics } from './diagnostics.ts';
|
||||
import { runAfterResponse, runBeforeError } from './hooks.ts';
|
||||
import { buildHttpFailure, buildOkOrValidation } from './results.ts';
|
||||
import type {
|
||||
HookContext,
|
||||
HttpHooks,
|
||||
HttpMethod,
|
||||
HttpResult,
|
||||
Out
|
||||
} from './types.ts';
|
||||
|
||||
interface ResolveHttpFinalResultInput<S extends StandardSchemaV1 | undefined> {
|
||||
readonly diagnostics: HttpDiagnostics;
|
||||
readonly hooks: Required<HttpHooks>;
|
||||
readonly method: HttpMethod;
|
||||
readonly fullUrl: string;
|
||||
readonly schema: S | undefined;
|
||||
readonly finalCtx: HookContext;
|
||||
readonly response: Response | undefined;
|
||||
readonly lastError: unknown;
|
||||
}
|
||||
|
||||
export async function resolveHttpFinalResult<S extends StandardSchemaV1 | undefined>(
|
||||
input: ResolveHttpFinalResultInput<S>
|
||||
): Promise<HttpResult<Out<S>>> {
|
||||
const { diagnostics, hooks, method, fullUrl, schema, finalCtx, response, lastError } = input;
|
||||
|
||||
if (response !== undefined) {
|
||||
// Success path may run afterResponse to swap the response.
|
||||
const finalResponse = await runAfterResponse(hooks.afterResponse, finalCtx, response);
|
||||
|
||||
if (finalResponse.ok) {
|
||||
const result = await buildOkOrValidation<S>(finalResponse, method, schema);
|
||||
// Validation failure on a 2xx body is a contract violation between
|
||||
// client and server — log at ERROR so it surfaces independently of
|
||||
// the result handling path.
|
||||
if (!result.ok && result.kind === HTTP_RESULT_KIND_VALIDATION) {
|
||||
emitHttpDiagnostic(diagnostics, HTTP_DIAGNOSTIC_EVENTS.RESPONSE_SCHEMA_FAILED, {
|
||||
method,
|
||||
url: fullUrl,
|
||||
issueCount: result.issues.length
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Non-2xx: try to rescue via beforeError, else build http result.
|
||||
const rescued = await runBeforeError(
|
||||
hooks.beforeError,
|
||||
{ ...finalCtx, response: finalResponse },
|
||||
lastError ?? new Error(httpStatusErrorMessage(finalResponse.status))
|
||||
);
|
||||
if (rescued instanceof Response) {
|
||||
return await buildOkOrValidation<S>(rescued, method, schema);
|
||||
}
|
||||
|
||||
// Log non-2xx as WARN — common enough to be expected (4xx mostly), but
|
||||
// still useful in DevTools for diagnosing routing/auth/rate limits.
|
||||
emitHttpDiagnostic(diagnostics, HTTP_DIAGNOSTIC_EVENTS.HTTP_STATUS, {
|
||||
method,
|
||||
url: fullUrl,
|
||||
status: finalResponse.status,
|
||||
statusText: finalResponse.statusText
|
||||
});
|
||||
return buildHttpFailure<S>(finalResponse, method);
|
||||
}
|
||||
|
||||
// No response: network or abort. Try beforeError rescue path.
|
||||
const rescued = await runBeforeError(hooks.beforeError, finalCtx, lastError);
|
||||
if (rescued instanceof Response) {
|
||||
return await buildOkOrValidation<S>(rescued, method, schema);
|
||||
}
|
||||
|
||||
return { ok: false, kind: HTTP_RESULT_KIND_NETWORK, error: lastError } as HttpResult<Out<S>>;
|
||||
}
|
||||
Loading…
Reference in new issue