feat: validate HTTP envelopes and payload schemas
This commit is contained in:
+33
-17
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user