feat: validate HTTP envelopes and payload schemas

This commit is contained in:
donghyeon-ka
2026-07-25 20:51:27 +09:00
parent 0934de44c7
commit bf813f09ab
4 changed files with 232 additions and 17 deletions
+33 -17
View File
@@ -1,6 +1,11 @@
import { systemClock } from "../../application/ports/clock-port.js";
import { getApiOperation } from "../../contracts/api-operations.js";
import { retryDelay, shouldRetry } from "./retry-policy.js";
import {
validateEnvelope,
validateOperationPayload,
validateOperationRequest,
} from "./schema-registry.js";
const noAuthSession =
/** @type {import("../../application/ports/auth-session-port.js").AuthSessionPort} */ ({
@@ -59,8 +64,7 @@ export function createHttpClient(dependencies) {
const clock = dependencies.clock ?? systemClock;
const random = dependencies.random ?? Math.random;
const validatePayload =
dependencies.validatePayload ??
((_schemaId, value) => ({ success: /** @type {true} */ (true), data: value }));
dependencies.validatePayload ?? validateOperationPayload;
const idempotencyKeyFactory =
dependencies.idempotencyKeyFactory ?? (() => crypto.randomUUID());
@@ -155,6 +159,21 @@ export function createHttpClient(dependencies) {
if (input.body !== undefined) headers.set("Content-Type", "application/json");
if (idempotencyKey) headers.set("Idempotency-Key", idempotencyKey);
if (input.body !== undefined) {
const requestValidation = validateOperationRequest(
operation.requestSchema,
input.body,
);
if (!requestValidation.success) {
return {
ok: false,
error: failure("VALIDATION_REJECTED", operation.operationId, attempt, {
code: "REQUEST_SCHEMA_INVALID",
}),
};
}
}
let request = new Request(new URL(operation.path, dependencies.baseUrl), {
method: operation.method,
headers,
@@ -263,27 +282,24 @@ async function parseResponse(response, operation, attempt, validatePayload) {
};
}
if (!envelope || typeof envelope !== "object") {
const envelopeValidation = validateEnvelope(envelope);
if (!envelopeValidation.success) {
return {
ok: false,
error: failure("ENVELOPE_MISMATCH", operation.operationId, attempt, {
code: "ENVELOPE_MISMATCH",
error: failure(
response.ok ? "ENVELOPE_MISMATCH" : statusKind(response.status),
operation.operationId,
attempt,
{
code: response.ok ? "ENVELOPE_MISMATCH" : "HTTP_FAILURE",
httpStatus: response.status,
}),
};
}
const envelopeRecord = /** @type {Record<string, unknown>} */ (envelope);
if (typeof envelopeRecord.success !== "boolean") {
return {
ok: false,
error: failure("ENVELOPE_MISMATCH", operation.operationId, attempt, {
code: "ENVELOPE_MISMATCH",
httpStatus: response.status,
}),
},
),
};
}
const envelopeRecord =
/** @type {Record<string, unknown>} */ (envelopeValidation.data);
if (response.ok && envelopeRecord.success === true && "data" in envelopeRecord) {
const payload = validatePayload(operation.responseSchema, envelopeRecord.data);
if (!payload.success) {