Error Handling

Crous provides a structured error hierarchy that maps C-level error codes to JavaScript exceptions. Every error includes a descriptive message to help you diagnose issues quickly.

Exception Hierarchy

Error
└── CrousError
├── CrousEncodeError
└── CrousDecodeError
  • CrousError — Base class for all Crous errors
  • CrousEncodeError — Thrown during serialization (dumps, dump)
  • CrousDecodeError — Thrown during deserialization (loads, load)

Catching Errors

catch_errors.js
const crous = require('crous');

// ─── Encoding Errors ────────────────────────────

try {
    // Circular reference
    const obj = {};
    obj.self = obj;
    crous.dumps(obj);
} catch (e) {
    if (e instanceof crous.CrousEncodeError) {
        console.log('Encode error:', e.message);
    }
}

try {
    // Unsupported type (without default handler)
    crous.dumps(new Map([['a', 1]]));
} catch (e) {
    console.log(e.name);    // 'CrousEncodeError'
    console.log(e.message); // unsupported type details
}

// ─── Decoding Errors ────────────────────────────

try {
    // Invalid data
    crous.loads(Buffer.from([0xFF, 0x00, 0x00]));
} catch (e) {
    if (e instanceof crous.CrousDecodeError) {
        console.log('Decode error:', e.message);
    }
}

try {
    // Truncated data
    const valid = crous.dumps({ key: 'value' });
    crous.loads(valid.subarray(0, 5));
} catch (e) {
    console.log(e.name);    // 'CrousDecodeError'
}

// ─── Generic catch ──────────────────────────────

try {
    crous.loads(Buffer.from('garbage'));
} catch (e) {
    if (e instanceof crous.CrousError) {
        // Catches both encode and decode errors
        console.log('Crous error:', e.message);
    }
}

C Error Codes

Under the hood, the C core returns numeric error codes that the Node.js binding translates to JavaScript exceptions. Understanding these codes helps when debugging low-level issues.

CodeNameDescription
0CROUS_OKNo error — success
1CROUS_ERR_MEMORYMemory allocation failed
2CROUS_ERR_ENCODEGeneral encoding error
3CROUS_ERR_DECODEGeneral decoding error
4CROUS_ERR_OVERFLOWBuffer or integer overflow
5CROUS_ERR_INVALID_TYPEUnsupported type encountered
6CROUS_ERR_INVALID_DATAMalformed or corrupted data
7CROUS_ERR_IOFile or stream I/O error
8CROUS_ERR_KEY_TYPENon-string dictionary key
9CROUS_ERR_NULL_INPUTNull pointer passed to API
10CROUS_ERR_VERSIONFormat version mismatch
11CROUS_ERR_DEPTH_EXCEEDEDMaximum nesting depth exceeded

Common Error Scenarios

Unsupported Types

unsupported_type.js
const crous = require('crous');

// Map is not supported natively
try {
    crous.dumps(new Map([['key', 'value']]));
} catch (e) {
    console.log(e.message);
    // Fix: convert to plain object
    const map = new Map([['key', 'value']]);
    crous.dumps(Object.fromEntries(map)); // ✓ works
}

// Functions cannot be serialized
try {
    crous.dumps({ handler: () => {} });
} catch (e) {
    console.log(e.message); // function type not supported
}

// Symbol keys
try {
    crous.dumps({ [Symbol('id')]: 42 });
} catch (e) {
    console.log(e.message); // invalid key type
}

Circular References

circular.js
const crous = require('crous');

// Direct circular reference
const a = {};
a.self = a;

try {
    crous.dumps(a);
} catch (e) {
    console.log(e.name); // CrousEncodeError
    // Max depth exceeded due to infinite recursion
}

// Indirect circular reference
const x = { name: 'x' };
const y = { name: 'y', ref: x };
x.ref = y;

try {
    crous.dumps(x);
} catch (e) {
    console.log(e.message); // depth exceeded
}

Depth Limit

Crous enforces a maximum nesting depth to protect against circular references and stack overflow. Deeply nested but non-circular structures may also trigger this limit.

Corrupted Data

corrupted.js
const crous = require('crous');

// Random bytes
try {
    crous.loads(Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]));
} catch (e) {
    console.log(e.name); // CrousDecodeError
}

// Truncated valid data
const valid = crous.dumps({ big: 'data'.repeat(1000) });
try {
    crous.loads(valid.subarray(0, 10));
} catch (e) {
    console.log(e.name); // CrousDecodeError
}

// Empty buffer
try {
    crous.loads(Buffer.alloc(0));
} catch (e) {
    console.log(e.name); // CrousDecodeError
}

Best Practices

1. Use Specific Error Classes

best_specific.js
const crous = require('crous');

function safeSerialize(data) {
    try {
        return crous.dumps(data);
    } catch (e) {
        if (e instanceof crous.CrousEncodeError) {
            console.error('Serialization failed:', e.message);
            return null;
        }
        throw e; // Re-throw unexpected errors
    }
}

function safeDeserialize(buffer) {
    try {
        return crous.loads(buffer);
    } catch (e) {
        if (e instanceof crous.CrousDecodeError) {
            console.error('Deserialization failed:', e.message);
            return null;
        }
        throw e;
    }
}

2. Validate Before Serializing

best_validate.js
function validateData(data) {
    if (data === undefined) {
        throw new Error('Data cannot be undefined at top level');
    }
    if (typeof data === 'function') {
        throw new Error('Functions cannot be serialized');
    }
    if (data instanceof Map) {
        throw new Error('Convert Map to Object first');
    }
    return true;
}

3. Use default for Graceful Fallbacks

best_default.js
const crous = require('crous');

const encoder = new crous.CrousEncoder({
    default: (obj) => {
        // Handle Dates
        if (obj instanceof Date) {
            return { __type: 'Date', value: obj.toISOString() };
        }
        // Handle Maps
        if (obj instanceof Map) {
            return { __type: 'Map', entries: [...obj.entries()] };
        }
        // Handle RegExp
        if (obj instanceof RegExp) {
            return { __type: 'RegExp', source: obj.source, flags: obj.flags };
        }
        // Unknown → log and skip
        console.warn(`Skipping unserializable: ${obj?.constructor?.name}`);
        return null;
    }
});