Files
defiQUG e8ae376e90 Enhance API services with validation and error handling improvements
- Integrated Zod validation schemas across various API routes to ensure input integrity and improve error handling.
- Updated `mapping-service`, `orchestrator`, `packet-service`, and `webhook-service` to utilize validation middleware for request parameters and bodies.
- Improved error handling in webhook management, packet generation, and compliance routes to provide clearer feedback on request failures.
- Added new validation schemas for various endpoints, enhancing overall API robustness and maintainability.
- Updated dependencies in `package.json` to include the new validation library.
2025-12-12 20:23:45 -08:00

276 lines
8.7 KiB
TypeScript

/**
* Request validation schemas using Zod
* Provides type-safe validation for all API endpoints
*/
import { z } from 'zod';
// Common patterns
const ethereumAddress = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address');
const accountRefId = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid account reference ID');
const walletRefId = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid wallet reference ID');
const tokenCode = z.string().regex(/^[A-Z0-9]{1,10}$/, 'Invalid token code');
const lienId = z.string().regex(/^[0-9]+$/, 'Invalid lien ID');
const triggerId = z.string().regex(/^[a-fA-F0-9]{64}$/, 'Invalid trigger ID');
const packetId = z.string().regex(/^[a-fA-F0-9]{64}$/, 'Invalid packet ID');
const lockId = z.string().regex(/^[a-fA-F0-9]{64}$/, 'Invalid lock ID');
const txHash = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid transaction hash');
const jurisdictionHash = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Invalid jurisdiction hash');
const amount = z.string().min(1, 'Amount is required');
const isoMsgType = z.string().regex(/^[a-z]+\.[0-9]{3}$/, 'Invalid ISO-20022 message type');
// Enums
const railEnum = z.enum(['FEDWIRE', 'SWIFT', 'SEPA', 'RTGS']);
const lienModeEnum = z.enum(['OFF', 'HARD_FREEZE', 'ENCUMBERED']);
const triggerStateEnum = z.enum(['CREATED', 'VALIDATED', 'SUBMITTED_TO_RAIL', 'PENDING', 'SETTLED', 'REJECTED', 'CANCELLED', 'RECALLED']);
const packetChannelEnum = z.enum(['PDF', 'AS4', 'EMAIL', 'PORTAL']);
const packetStatusEnum = z.enum(['GENERATED', 'DISPATCHED', 'DELIVERED', 'ACKNOWLEDGED', 'FAILED']);
const ackStatusEnum = z.enum(['RECEIVED', 'ACCEPTED', 'REJECTED']);
const txStatusEnum = z.enum(['PENDING', 'SUCCESS', 'FAILED']);
// Token schemas
export const deployTokenSchema = z.object({
name: z.string().min(1, 'Name is required'),
symbol: tokenCode,
decimals: z.number().int().min(0).max(255),
issuer: ethereumAddress,
defaultLienMode: lienModeEnum.optional().default('ENCUMBERED'),
bridgeOnly: z.boolean().optional().default(false),
bridge: ethereumAddress.optional(),
});
export const updatePolicySchema = z.object({
paused: z.boolean().optional(),
bridgeOnly: z.boolean().optional(),
bridge: ethereumAddress.optional(),
lienMode: lienModeEnum.optional(),
forceTransferMode: z.boolean().optional(),
routes: z.array(railEnum).optional(),
});
export const mintRequestSchema = z.object({
to: ethereumAddress,
amount: amount,
reasonCode: z.string().optional(),
});
export const burnRequestSchema = z.object({
from: ethereumAddress,
amount: amount,
reasonCode: z.string().optional(),
});
export const clawbackRequestSchema = z.object({
from: ethereumAddress,
to: ethereumAddress,
amount: amount,
reasonCode: z.string().optional(),
});
export const forceTransferRequestSchema = z.object({
from: ethereumAddress,
to: ethereumAddress,
amount: amount,
reasonCode: z.string().optional(),
});
// Lien schemas
export const placeLienSchema = z.object({
debtor: z.string().min(1, 'Debtor is required'),
amount: amount,
expiry: z.number().int().min(0).optional(),
priority: z.number().int().min(0).max(255).optional(),
reasonCode: z.string().optional(),
});
export const reduceLienSchema = z.object({
reduceBy: amount,
});
// Compliance schemas
export const setComplianceSchema = z.object({
allowed: z.boolean(),
riskTier: z.number().int().min(0).max(255).optional(),
jurisdictionHash: jurisdictionHash.optional(),
});
export const setFrozenSchema = z.object({
frozen: z.boolean(),
});
export const setTierSchema = z.object({
tier: z.number().int().min(0).max(255),
});
export const setJurisdictionHashSchema = z.object({
jurisdictionHash: jurisdictionHash,
});
// Mapping schemas
export const linkAccountWalletSchema = z.object({
accountRefId: accountRefId,
walletRefId: walletRefId,
provider: z.string().optional(),
metadata: z.record(z.any()).optional(),
});
export const unlinkAccountWalletSchema = z.object({
accountRefId: accountRefId,
walletRefId: walletRefId,
});
// ISO schemas
export const submitInboundMessageSchema = z.object({
msgType: isoMsgType,
instructionId: z.string().min(1, 'Instruction ID is required'),
endToEndId: z.string().optional(),
payloadHash: z.string().min(1, 'Payload hash is required'),
payload: z.string().min(1, 'Payload is required'),
rail: railEnum.optional(),
});
export const submitOutboundMessageSchema = z.object({
msgType: isoMsgType,
instructionId: z.string().min(1, 'Instruction ID is required'),
endToEndId: z.string().optional(),
payloadHash: z.string().min(1, 'Payload hash is required'),
payload: z.string().min(1, 'Payload is required'),
rail: railEnum.optional(),
token: ethereumAddress,
amount: amount,
accountRefId: z.string().min(1, 'Account reference ID is required'),
counterpartyRefId: z.string().min(1, 'Counterparty reference ID is required'),
});
// Packet schemas
export const generatePacketSchema = z.object({
triggerId: z.string().min(1, 'Trigger ID is required'),
channel: packetChannelEnum,
options: z.record(z.any()).optional(),
});
export const dispatchPacketSchema = z.object({
channel: z.enum(['EMAIL', 'AS4', 'PORTAL']),
recipient: z.string().min(1, 'Recipient is required'),
});
export const acknowledgePacketSchema = z.object({
status: ackStatusEnum,
ackId: z.string().optional(),
});
// Bridge schemas
export const bridgeLockSchema = z.object({
token: ethereumAddress,
amount: amount,
targetChain: z.string().min(1, 'Target chain is required'),
targetRecipient: ethereumAddress,
});
export const bridgeUnlockSchema = z.object({
lockId: z.string().min(1, 'Lock ID is required'),
token: ethereumAddress,
to: ethereumAddress,
amount: amount,
sourceChain: z.string().min(1, 'Source chain is required'),
sourceTx: z.string().min(1, 'Source transaction is required'),
proof: z.string().min(1, 'Proof is required'),
});
// Trigger schemas
export const markSubmittedSchema = z.object({
railTxRef: z.string().min(1, 'Rail transaction reference is required'),
});
export const confirmRejectedSchema = z.object({
reason: z.string().optional(),
});
// Query parameter schemas
export const listTokensQuerySchema = z.object({
code: tokenCode.optional(),
issuer: ethereumAddress.optional(),
limit: z.coerce.number().int().min(1).max(100).default(20),
offset: z.coerce.number().int().min(0).default(0),
});
export const listLiensQuerySchema = z.object({
debtor: z.string().optional(),
active: z.coerce.boolean().optional(),
limit: z.coerce.number().int().min(1).max(100).default(20),
offset: z.coerce.number().int().min(0).default(0),
});
export const listPacketsQuerySchema = z.object({
triggerId: z.string().optional(),
instructionId: z.string().optional(),
status: packetStatusEnum.optional(),
limit: z.coerce.number().int().min(1).max(100).default(20),
offset: z.coerce.number().int().min(0).default(0),
});
export const listTriggersQuerySchema = z.object({
state: triggerStateEnum.optional(),
rail: railEnum.optional(),
msgType: isoMsgType.optional(),
instructionId: z.string().optional(),
accountRef: z.string().optional(),
walletRef: z.string().optional(),
limit: z.coerce.number().int().min(1).max(100).default(20),
offset: z.coerce.number().int().min(0).default(0),
});
export const getEncumbranceQuerySchema = z.object({
token: ethereumAddress.optional(),
});
// Web3-IBAN schemas (for mapping-service)
export const addressToIBANSchema = z.object({
address: z.string().min(1, 'Address is required'),
});
export const ibanToAddressSchema = z.object({
iban: z.string().min(1, 'IBAN is required'),
});
export const validateIBANSchema = z.object({
iban: z.string().min(1, 'IBAN is required'),
});
export const validateAddressSchema = z.object({
address: z.string().min(1, 'Address is required'),
});
// Webhook schemas
export const createWebhookSchema = z.object({
url: z.string().url('Invalid URL'),
events: z.array(z.string()).min(1, 'At least one event is required'),
secret: z.string().optional(),
active: z.boolean().optional().default(true),
});
export const updateWebhookSchema = z.object({
url: z.string().url('Invalid URL').optional(),
events: z.array(z.string()).optional(),
secret: z.string().optional(),
active: z.boolean().optional(),
});
export const replayWebhooksQuerySchema = z.object({
since: z.string().optional(),
});
export const listWebhooksQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(100).default(20),
offset: z.coerce.number().int().min(0).default(0),
});
export const getDLQEntriesQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(100).default(20),
offset: z.coerce.number().int().min(0).default(0),
});
// Provider connection schema
export const connectProviderSchema = z.record(z.any());