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
└── 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.
| Code | Name | Description |
|---|---|---|
| 0 | CROUS_OK | No error — success |
| 1 | CROUS_ERR_MEMORY | Memory allocation failed |
| 2 | CROUS_ERR_ENCODE | General encoding error |
| 3 | CROUS_ERR_DECODE | General decoding error |
| 4 | CROUS_ERR_OVERFLOW | Buffer or integer overflow |
| 5 | CROUS_ERR_INVALID_TYPE | Unsupported type encountered |
| 6 | CROUS_ERR_INVALID_DATA | Malformed or corrupted data |
| 7 | CROUS_ERR_IO | File or stream I/O error |
| 8 | CROUS_ERR_KEY_TYPE | Non-string dictionary key |
| 9 | CROUS_ERR_NULL_INPUT | Null pointer passed to API |
| 10 | CROUS_ERR_VERSION | Format version mismatch |
| 11 | CROUS_ERR_DEPTH_EXCEEDED | Maximum 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;
}
});