Source

src/util/errors.js

'use strict';

/**
 * Custom error classes following clean code principles
 * Provides clear, actionable error messages with context
 * @category Error Classes
 */

/**
 * Base class for all CSV parsing errors
 * Provides consistent error formatting and context
 * @category Error Classes
 */
class CsvParsingError extends Error {
    /**
     * Create a CSV parsing error
     * @param {string} message - Error message
     * @param {string} code - Error code for identification
     * @param {object} context - Additional context information (default: {})
     */
    constructor(message, code, context = {}) {
        super(message);
        this.name = 'CsvParsingError';
        this.code = code;
        this.context = context;
        Error.captureStackTrace(this, this.constructor);
    }

    /**
     * Convert error to formatted string with context information
     * @returns {string} Formatted error message including context
     */
    toString() {
        let output = `${this.name}: ${this.message}`;
        
        if (this.context && Object.keys(this.context).length > 0) {
            output += '\n\nContext:';
            Object.entries(this.context).forEach(([key, value]) => {
                output += `\n  ${key}: ${this.formatValue(value)}`;
            });
        }
        
        return output;
    }

    /**
     * Format a context value for display in error message
     * @param {unknown} value - Value to format
     * @returns {string} Formatted value string
     * @private
     */
    formatValue(value) {
        if (value === null) return 'null';
        if (value === undefined) return 'undefined';
        if (typeof value === 'string') return `"${value}"`;
        if (typeof value === 'object') return JSON.stringify(value);
        return String(value);
    }
}

/**
 * Input validation errors
 * Thrown when function parameters don't meet expected type or value requirements
 * @category Error Classes
 */
class InputValidationError extends CsvParsingError {
    /**
     * Create an input validation error
     * @param {string} paramName - Name of the invalid parameter
     * @param {string} expectedType - Expected type description
     * @param {string} receivedType - Actual type received
     * @param {string} details - Additional error details (optional)
     */
    constructor(paramName, expectedType, receivedType, details = '') {
        const message = 
            `Invalid input: Parameter '${paramName}' is required.\n` +
            `Expected: ${expectedType}\n` +
            `Received: ${receivedType}${details ? '\n' + details : ''}`;
        
        super(message, 'INPUT_VALIDATION_ERROR', {
            parameter: paramName,
            expectedType,
            receivedType
        });
        this.name = 'InputValidationError';
    }
}

/**
 * Configuration-related errors
 * Thrown when configuration options conflict or are invalid
 * @category Error Classes
 */
class ConfigurationError extends CsvParsingError {
    /**
     * Create a configuration error
     * @param {string} message - Error message
     * @param {object} conflictingOptions - Configuration options in conflict (optional)
     */
    constructor(message, conflictingOptions = {}) {
        super(message, 'CONFIGURATION_ERROR', conflictingOptions);
        this.name = 'ConfigurationError';
    }

    /**
     * Create error for quoted field configuration conflict
     * Occurs when quote character is used as delimiter while quoted fields are enabled
     * @param {string} optionName - Name of the conflicting option
     * @param {string} value - Value that causes the conflict
     * @returns {ConfigurationError} Configured error instance
     * @static
     */
    static quotedFieldConflict(optionName, value) {
        return new ConfigurationError(
            `Configuration conflict: supportQuotedField() is enabled, but ${optionName} is set to '${value}'.\n` +
            `The quote character (") cannot be used as a field delimiter, separator, or sub-array delimiter when quoted field support is active.\n\n` +
            `Solutions:\n` +
            `  1. Use a different character for ${optionName} (e.g., '|', '\\t', ';')\n` +
            `  2. Disable supportQuotedField() if your CSV doesn't contain quoted fields\n` +
            `  3. Refer to RFC 4180 for proper CSV formatting: https://tools.ietf.org/html/rfc4180`,
            { optionName, value, conflictingOption: 'supportQuotedField' }
        );
    }

    /**
     * Create error for invalid header index
     * Occurs when indexHeader() receives non-numeric value
     * @param {unknown} value - Invalid header index value
     * @returns {ConfigurationError} Configured error instance
     * @static
     */
    static invalidHeaderIndex(value) {
        return new ConfigurationError(
            `Invalid configuration: indexHeader() expects a numeric value.\n` +
            `Received: ${typeof value} (${value})\n\n` +
            `Solutions:\n` +
            `  1. Ensure indexHeader() receives a number: indexHeader(0), indexHeader(1), etc.\n` +
            `  2. Headers are typically found on row 0 (first line)\n` +
            `  3. Use indexHeader(2) if headers are on the 3rd line`,
            { parameterName: 'indexHeader', value, type: typeof value }
        );
    }
}

/**
 * CSV parsing errors with detailed context
 * Thrown when CSV format is invalid or malformed
 * @category Error Classes
 */
class CsvFormatError extends CsvParsingError {
    /**
     * Create a CSV format error
     * @param {string} message - Error message
     * @param {object} context - Additional context information (optional)
     */
    constructor(message, context = {}) {
        super(message, 'CSV_FORMAT_ERROR', context);
        this.name = 'CsvFormatError';
    }

    /**
     * Create error for missing CSV header row
     * Occurs when no valid header row is found in CSV
     * @returns {CsvFormatError} Configured error instance
     * @static
     */
    static missingHeader() {
        return new CsvFormatError(
            `CSV parsing error: No header row found.\n` +
            `The CSV file appears to be empty or has no valid header line.\n\n` +
            `Solutions:\n` +
            `  1. Ensure your CSV file contains at least one row (header row)\n` +
            `  2. Verify the file is not empty or contains only whitespace\n` +
            `  3. Check if you need to use indexHeader(n) to specify a non-standard header row\n` +
            `  4. Refer to RFC 4180 for proper CSV format: https://tools.ietf.org/html/rfc4180`
        );
    }

    /**
     * Create error for mismatched quotes in CSV
     * Occurs when quoted fields are not properly closed
     * @param {string} location - Where the error occurred (default: 'CSV')
     * @returns {CsvFormatError} Configured error instance
     * @static
     */
    static mismatchedQuotes(location = 'CSV') {
        return new CsvFormatError(
            `CSV parsing error: Mismatched quotes detected in ${location}.\n` +
            `A quoted field was not properly closed with a matching quote character.\n\n` +
            `RFC 4180 rules for quoted fields:\n` +
            `  • Fields containing delimiters or quotes MUST be enclosed in double quotes\n` +
            `  • To include a quote within a quoted field, use two consecutive quotes: ""\n` +
            `  • Example: "Smith, John" (name contains comma)\n` +
            `  • Example: "He said ""Hello""" (text contains quotes)\n\n` +
            `Solutions:\n` +
            `  1. Review your CSV for properly paired quote characters\n` +
            `  2. Use double quotes ("") to escape quotes within quoted fields\n` +
            `  3. Ensure all commas within field values are inside quotes\n` +
            `  4. Enable supportQuotedField(true) if you're using quoted fields`,
            { location }
        );
    }
}

/**
 * File operation errors
 * Thrown when file read or write operations fail
 * @category Error Classes
 */
class FileOperationError extends CsvParsingError {
    /**
     * Create a file operation error
     * @param {string} operation - Type of operation that failed (e.g., 'read', 'write')
     * @param {string} filePath - Path to the file where operation failed
     * @param {Error} originalError - The underlying error object from Node.js
     */
    constructor(operation, filePath, originalError) {
        const message = 
            `File operation error: Failed to ${operation} file.\n` +
            `File path: ${filePath}\n` +
            `Reason: ${originalError.message}\n\n` +
            `Solutions:\n` +
            `  1. Verify the file path is correct: ${filePath}\n` +
            `  2. Check file permissions (read access for input, write access for output)\n` +
            `  3. Ensure the directory exists and is writable for output files\n` +
            `  4. Verify the file is not in use by another process`;
        
        super(message, 'FILE_OPERATION_ERROR', {
            operation,
            filePath,
            originalError: originalError.message
        });
        this.name = 'FileOperationError';
        this.originalError = originalError;
    }
}

/**
 * JSON validation errors
 * Thrown when parsed CSV data cannot be converted to valid JSON
 * @category Error Classes
 */
class JsonValidationError extends CsvParsingError {
    /**
     * Create a JSON validation error
     * @param {string} csvData - The CSV data that failed validation
     * @param {Error} originalError - The underlying JSON parsing error
     */
    constructor(csvData, originalError) {
        const message = 
            `JSON validation error: The parsed CSV data generated invalid JSON.\n` +
            `This typically indicates malformed field names or values in the CSV.\n` +
            `Original error: ${originalError.message}\n\n` +
            `Solutions:\n` +
            `  1. Check that field names are valid JavaScript identifiers (or will be converted safely)\n` +
            `  2. Review the CSV data for special characters that aren't properly escaped\n` +
            `  3. Enable supportQuotedField(true) for fields containing special characters\n` +
            `  4. Verify that formatValueByType() isn't converting values incorrectly`;
        
        super(message, 'JSON_VALIDATION_ERROR', {
            originalError: originalError.message,
            csvPreview: csvData ? csvData.substring(0, 200) : 'N/A'
        });
        this.name = 'JsonValidationError';
        this.originalError = originalError;
    }
}

/**
 * Browser-specific errors
 * Thrown when browser API operations fail
 * @category Error Classes
 */
class BrowserApiError extends CsvParsingError {
    /**
     * Create a browser API error
     * @param {string} message - Error message
     * @param {object} context - Additional context information (optional)
     */
    constructor(message, context = {}) {
        super(message, 'BROWSER_API_ERROR', context);
        this.name = 'BrowserApiError';
    }

    /**
     * Create error for unavailable FileReader API
     * Occurs when browser doesn't support FileReader
     * @returns {BrowserApiError} Configured error instance
     * @static
     */
    static fileReaderNotAvailable() {
        return new BrowserApiError(
            `Browser compatibility error: FileReader API is not available.\n` +
            `Your browser does not support the FileReader API required for file parsing.\n\n` +
            `Solutions:\n` +
            `  1. Use a modern browser that supports FileReader (Chrome 13+, Firefox 10+, Safari 6+)\n` +
            `  2. Consider using csvStringToJson() or csvStringToJsonAsync() for string-based parsing\n` +
            `  3. Implement a polyfill or alternative file reading method`
        );
    }

    /**
     * Create error for file parsing failure in browser
     * Occurs when file read or CSV parse fails
     * @param {Error} originalError - The underlying error that occurred
     * @returns {BrowserApiError} Configured error instance
     * @static
     */
    static parseFileError(originalError) {
        return new BrowserApiError(
            `Browser file parsing error: Failed to read and parse the file.\n` +
            `Error details: ${originalError.message}\n\n` +
            `Solutions:\n` +
            `  1. Verify the file is a valid CSV file\n` +
            `  2. Check the file encoding (UTF-8 is recommended)\n` +
            `  3. Try a smaller file to isolate the issue\n` +
            `  4. Check browser console for additional error details`,
            { originalError: originalError.message }
        );
    }
}

module.exports = {
    CsvParsingError,
    InputValidationError,
    ConfigurationError,
    CsvFormatError,
    FileOperationError,
    JsonValidationError,
    BrowserApiError
};