BREAKING CHANGE(core): Rebrand XInvoice to EInvoice: update package name, class names, imports, and documentation
This commit is contained in:
341
ts/errors.ts
Normal file
341
ts/errors.ts
Normal file
@ -0,0 +1,341 @@
|
||||
/**
|
||||
* 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(/</g, (match, offset) => {
|
||||
// Don't replace if it's part of <![CDATA[
|
||||
const before = cleanedXml.substring(Math.max(0, offset - 10), offset);
|
||||
if (before.includes('CDATA[')) return match;
|
||||
return '<';
|
||||
});
|
||||
|
||||
// Try to fix unclosed tags if we have location info
|
||||
if (error.line && error.xmlSnippet) {
|
||||
// This is a simplified approach - real implementation would be more sophisticated
|
||||
const lines = cleanedXml.split('\n');
|
||||
if (lines[error.line - 1]) {
|
||||
// Attempt basic fixes based on the error
|
||||
// This is just a placeholder for more sophisticated recovery
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
cleanedXml,
|
||||
message: 'Applied basic XML cleaning and encoding fixes'
|
||||
};
|
||||
} catch (recoveryError) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides partial data extraction on validation failure
|
||||
*/
|
||||
public static extractPartialData(
|
||||
xmlString: string,
|
||||
format: string
|
||||
): { success: boolean; partialData?: any; message: string } {
|
||||
try {
|
||||
// This would implement format-specific partial extraction
|
||||
// For now, returning a placeholder
|
||||
return {
|
||||
success: false,
|
||||
message: 'Partial data extraction not yet implemented'
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Partial extraction failed: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error context builder for detailed error information
|
||||
*/
|
||||
export class ErrorContext {
|
||||
private context: Map<string, any> = 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<string, any> {
|
||||
return Object.fromEntries(this.context);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user