From 37a5e008f52de96f3a20ef5b192103197b0363cb Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 29 Apr 2026 20:14:59 +0200 Subject: [PATCH] Extract http response resolution --- NEXT_STEPS.md | 1 + src/arts/http/engine-http.ts | 77 ++++---------------------- src/arts/http/response-resolution.ts | 82 ++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 66 deletions(-) create mode 100644 src/arts/http/response-resolution.ts diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index dee33ae..b88ea30 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -31,6 +31,7 @@ Estado al cierre: - `arts/conn/connection.ts` delega la programacion de reconnect y exhaustion en `connection-reconnect-runtime.ts`. - `libs/perm/evaluator.ts` delega helpers puros de resultado, dependencias y comparacion en `evaluator-helpers.ts`. - `arts/http/engine-http.ts` delega la validacion preflight de `bodySchema` en `request-validation.ts`. + - `arts/http/engine-http.ts` delega la resolucion final de response/error en `response-resolution.ts`. - `arts/sium/core/pipe.ts` queda centrado en composicion; factories `refine/transform/codec/meta` viven en `steps.ts`. - `arts/conn/connection.ts` delega decode/routing de frames entrantes en `connection-message-router.ts`. - `arts/conn/connection.ts` delega el intento open/auth/flush/join en `connection-connect.ts`. diff --git a/src/arts/http/engine-http.ts b/src/arts/http/engine-http.ts index bb99f58..b876a38 100644 --- a/src/arts/http/engine-http.ts +++ b/src/arts/http/engine-http.ts @@ -10,9 +10,6 @@ import { HTTP_METHOD_PATCH, HTTP_METHOD_POST, HTTP_METHOD_PUT, - HTTP_RESULT_KIND_NETWORK, - HTTP_RESULT_KIND_VALIDATION, - httpStatusErrorMessage, HTTP_DIAGNOSTIC_EVENTS } from './consts.ts'; import { emitHttpDiagnostic } from './diagnostics.ts'; @@ -24,14 +21,12 @@ import { type ResolvedHttpDefaults } from './engine-options.ts'; import { - runAfterResponse, - runBeforeError, runBeforeRequest, runBeforeRetry } from './hooks.ts'; import { computeRetryDelay, delayWithSignal, shouldRetryRequest } from './retry.ts'; -import { buildHttpFailure, buildOkOrValidation } from './results.ts'; import { validateHttpRequestBody } from './request-validation.ts'; +import { resolveHttpFinalResult } from './response-resolution.ts'; import { appendSearch, normalizeSearch, resolveUrl } from './search.ts'; import { attemptTimeoutSignal, @@ -48,7 +43,6 @@ import type { HttpGetInit, HttpInit, HttpMethod, - HttpResult, HttpResultPromise, Out } from './types.ts'; @@ -240,65 +234,16 @@ async function execute( // shipped. const finalCtx: HookContext = lastCtx!; - 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( - finalResponse, - method, - init !== undefined ? init.schema : undefined - ); - // 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( - rescued, - method, - init !== undefined ? init.schema : undefined - ); - } - - // 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(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( - rescued, - method, - init !== undefined ? init.schema : undefined - ); - } - - return { ok: false, kind: HTTP_RESULT_KIND_NETWORK, error: lastError } as HttpResult>; + return resolveHttpFinalResult({ + diagnostics, + hooks, + method, + fullUrl, + schema: init !== undefined ? init.schema : undefined, + finalCtx, + response, + lastError + }); } function classifyFetchError(err: unknown, signal: AbortSignal): unknown { diff --git a/src/arts/http/response-resolution.ts b/src/arts/http/response-resolution.ts new file mode 100644 index 0000000..a0546e2 --- /dev/null +++ b/src/arts/http/response-resolution.ts @@ -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 { + readonly diagnostics: HttpDiagnostics; + readonly hooks: Required; + readonly method: HttpMethod; + readonly fullUrl: string; + readonly schema: S | undefined; + readonly finalCtx: HookContext; + readonly response: Response | undefined; + readonly lastError: unknown; +} + +export async function resolveHttpFinalResult( + input: ResolveHttpFinalResultInput +): Promise>> { + 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(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(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(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(rescued, method, schema); + } + + return { ok: false, kind: HTTP_RESULT_KIND_NETWORK, error: lastError } as HttpResult>; +}