/** * Base error class for all EInvoice-related errors */ export class EInvoiceError extends Error { public code: string; public details?: any; public cause?: Error; constructor(message: string, code: string, details?: any, cause?: Error) { super(message); this.name = 'EInvoiceError'; this.code = code; this.details = details; this.cause = cause; // Maintains proper stack trace for where our error was thrown (only available on V8) if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } /** * Returns a detailed error message including cause if available */ public getDetailedMessage(): string { let message = `${this.name} [${this.code}]: ${this.message}`; if (this.details) { message += `\nDetails: ${JSON.stringify(this.details, null, 2)}`; } if (this.cause) { message += `\nCaused by: ${this.cause.message}`; } return message; } } /** * Error thrown when XML parsing fails */ export class EInvoiceParsingError extends EInvoiceError { public line?: number; public column?: number; public xmlSnippet?: string; constructor( message: string, details?: { line?: number; column?: number; xmlSnippet?: string; format?: string; }, cause?: Error ) { super(message, 'PARSE_ERROR', details, cause); this.name = 'EInvoiceParsingError'; this.line = details?.line; this.column = details?.column; this.xmlSnippet = details?.xmlSnippet; } /** * Returns a user-friendly error message with location information */ public getLocationMessage(): string { let message = this.message; if (this.line !== undefined && this.column !== undefined) { message += ` at line ${this.line}, column ${this.column}`; } if (this.xmlSnippet) { message += `\nNear: ${this.xmlSnippet}`; } return message; } } /** * Error thrown when validation fails */ export class EInvoiceValidationError extends EInvoiceError { public validationErrors: Array<{ code: string; message: string; location?: string; severity?: 'error' | 'warning'; }>; constructor( message: string, validationErrors: Array<{ code: string; message: string; location?: string; severity?: 'error' | 'warning'; }>, details?: any ) { super(message, 'VALIDATION_ERROR', details); this.name = 'EInvoiceValidationError'; this.validationErrors = validationErrors; } /** * Returns a formatted validation report */ public getValidationReport(): string { let report = `${this.message}\n\nValidation errors:\n`; for (const error of this.validationErrors) { report += `- [${error.code}] ${error.message}`; if (error.location) { report += ` (at ${error.location})`; } if (error.severity) { report += ` [${error.severity.toUpperCase()}]`; } report += '\n'; } return report; } /** * Gets validation errors by severity */ public getErrorsBySeverity(severity: 'error' | 'warning'): typeof this.validationErrors { return this.validationErrors.filter(e => (e.severity || 'error') === severity); } } /** * Error thrown during PDF operations */ export class EInvoicePDFError extends EInvoiceError { public operation: 'extract' | 'embed' | 'create' | 'validate'; public pdfInfo?: { filename?: string; size?: number; pageCount?: number; }; constructor( message: string, operation: 'extract' | 'embed' | 'create' | 'validate', details?: any, cause?: Error ) { super(message, `PDF_${operation.toUpperCase()}_ERROR`, details, cause); this.name = 'EInvoicePDFError'; this.operation = operation; this.pdfInfo = details?.pdfInfo; } /** * Returns recovery suggestions based on the operation */ public getRecoverySuggestions(): string[] { const suggestions: string[] = []; switch (this.operation) { case 'extract': suggestions.push( 'Ensure the PDF contains embedded XML data', 'Check if the PDF is a valid PDF/A-3 document', 'Try using a different extraction method', 'Verify the PDF is not corrupted' ); break; case 'embed': suggestions.push( 'Ensure the source PDF is valid', 'Check that the XML data is well-formed', 'Verify sufficient memory is available', 'Try with a smaller XML payload' ); break; case 'create': suggestions.push( 'Verify all required invoice data is provided', 'Check that the template PDF exists', 'Ensure write permissions for output directory' ); break; case 'validate': suggestions.push( 'Check PDF/A-3 compliance', 'Verify XML attachment structure', 'Ensure proper PDF metadata' ); break; } return suggestions; } } /** * Error thrown for format-specific issues */ export class EInvoiceFormatError extends EInvoiceError { public sourceFormat?: string; public targetFormat?: string; public unsupportedFeatures?: string[]; constructor( message: string, details: { sourceFormat?: string; targetFormat?: string; unsupportedFeatures?: string[]; conversionPath?: string; }, cause?: Error ) { super(message, 'FORMAT_ERROR', details, cause); this.name = 'EInvoiceFormatError'; this.sourceFormat = details.sourceFormat; this.targetFormat = details.targetFormat; this.unsupportedFeatures = details.unsupportedFeatures; } /** * Returns a compatibility report */ public getCompatibilityReport(): string { let report = this.message; if (this.sourceFormat && this.targetFormat) { report += `\n\nConversion: ${this.sourceFormat} → ${this.targetFormat}`; } if (this.unsupportedFeatures && this.unsupportedFeatures.length > 0) { report += '\n\nUnsupported features:'; for (const feature of this.unsupportedFeatures) { report += `\n- ${feature}`; } } return report; } } /** * Error recovery helper class */ export class ErrorRecovery { /** * Attempts to recover from a parsing error by cleaning the XML */ public static async attemptXMLRecovery( xmlString: string, error: EInvoiceParsingError ): Promise<{ success: boolean; cleanedXml?: string; message: string }> { try { let cleanedXml = xmlString; // Remove BOM if present if (cleanedXml.charCodeAt(0) === 0xFEFF) { cleanedXml = cleanedXml.slice(1); } // Fix common encoding issues cleanedXml = cleanedXml .replace(/&(?!(amp|lt|gt|apos|quot);)/g, '&') .replace(/ { // Don't replace if it's part of = new Map(); public add(key: string, value: any): this { this.context.set(key, value); return this; } public addTimestamp(): this { this.context.set('timestamp', new Date().toISOString()); return this; } public addEnvironment(): this { this.context.set('environment', { nodeVersion: process.version, platform: process.platform, arch: process.arch }); return this; } public build(): Record { return Object.fromEntries(this.context); } }