Files
brazil-swift-ops/packages/utils/src/input-validation.ts
defiQUG 7558268f9d Implement UI components and quick wins
- Complete Dashboard page with statistics, recent activity, compliance status
- Complete Transactions page with form, validation, E&O uplift display
- Complete Treasury page with account management
- Complete Reports page with BCB report generation and export
- Add LoadingSpinner component
- Add ErrorBoundary component
- Add Toast notification system
- Add comprehensive input validation
- Add error handling utilities
- Add basic unit tests structure
- Fix XML exporter TypeScript errors
- All quick wins completed
2026-01-23 16:32:41 -08:00

257 lines
5.8 KiB
TypeScript

/**
* Input validation utilities for user inputs and transaction data
*/
import { validateBrazilianTaxId } from './validation';
export interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}
/**
* Validate transaction amount
*/
export function validateAmount(amount: number | string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
if (isNaN(numAmount)) {
errors.push('Amount must be a valid number');
} else if (numAmount <= 0) {
errors.push('Amount must be greater than zero');
} else if (numAmount > 1000000000) {
warnings.push('Amount exceeds 1 billion - please verify');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate currency code (ISO 4217)
*/
export function validateCurrency(currency: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!currency || currency.trim().length === 0) {
errors.push('Currency code is required');
} else if (currency.length !== 3) {
errors.push('Currency code must be 3 characters (ISO 4217)');
} else if (!/^[A-Z]{3}$/.test(currency)) {
errors.push('Currency code must be uppercase letters only');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate email address
*/
export function validateEmail(email: string | undefined): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!email) {
return { valid: true, errors, warnings };
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
errors.push('Invalid email address format');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate phone number (basic validation)
*/
export function validatePhone(phone: string | undefined): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!phone) {
return { valid: true, errors, warnings };
}
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
if (!phoneRegex.test(phone)) {
errors.push('Invalid phone number format');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate account number or IBAN
*/
export function validateAccountNumber(
accountNumber: string | undefined,
iban: string | undefined
): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!accountNumber && !iban) {
errors.push('Either account number or IBAN is required');
return { valid: false, errors, warnings };
}
if (iban) {
// Basic IBAN validation (2 letters + 2 digits + up to 30 alphanumeric)
const ibanRegex = /^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/;
if (!ibanRegex.test(iban.replace(/\s/g, ''))) {
errors.push('Invalid IBAN format');
}
}
if (accountNumber && accountNumber.trim().length === 0) {
errors.push('Account number cannot be empty');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate purpose of payment
*/
export function validatePurposeOfPayment(
purpose: string | undefined
): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!purpose || purpose.trim().length === 0) {
errors.push('Purpose of payment is required');
} else if (purpose.trim().length < 5) {
warnings.push('Purpose of payment is very short - provide more detail');
} else if (purpose.length > 140) {
warnings.push('Purpose of payment exceeds 140 characters - may be truncated');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate name field
*/
export function validateName(name: string | undefined, fieldName: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!name || name.trim().length === 0) {
errors.push(`${fieldName} is required`);
} else if (name.trim().length < 2) {
errors.push(`${fieldName} must be at least 2 characters`);
} else if (name.length > 140) {
warnings.push(`${fieldName} exceeds 140 characters`);
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate address
*/
export function validateAddress(
address: string | undefined,
city: string | undefined,
country: string | undefined
): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!address && !city) {
errors.push('Either address or city is required');
}
if (!country) {
errors.push('Country is required');
} else if (country.length !== 2) {
errors.push('Country must be a 2-letter ISO code');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate tax ID (CPF or CNPJ)
*/
export function validateTaxId(taxId: string | undefined, fieldName: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!taxId) {
errors.push(`${fieldName} is required`);
return { valid: false, errors, warnings };
}
const validation = validateBrazilianTaxId(taxId);
if (!validation.valid) {
errors.push(`${fieldName}: ${validation.error || 'Invalid format'}`);
}
return {
valid: validation.valid,
errors,
warnings,
};
}
/**
* Sanitize string input (remove dangerous characters)
*/
export function sanitizeString(input: string): string {
return input
.replace(/[<>]/g, '') // Remove potential HTML/XML tags
.replace(/[\x00-\x1F\x7F]/g, '') // Remove control characters
.trim();
}
/**
* Sanitize number input
*/
export function sanitizeNumber(input: string | number): number {
if (typeof input === 'number') {
return isNaN(input) ? 0 : input;
}
const num = parseFloat(input.replace(/[^\d.-]/g, ''));
return isNaN(num) ? 0 : num;
}