๐ก️ Safe and Declarative Input Handling with ObjNavigator
๐ก️ Safe and Declarative Input Handling with ObjNavigator
When building distributed systems, WebSocket pipelines, or HTTP APIs, you constantly handle data from outside your trust boundary. Query parameters, JSON payloads, and message frames often arrive as strings, may be malformed, and should never be consumed directly by business logic.
Before using input, it should be:
- Coerced into runtime types (numbers, booleans, dates, etc.)
- Validated against business rules
- Handled safely when invalid — often without throwing
In asynchronous or message-driven systems, throwing exceptions is often impractical. Errors must be reported, emitted, or logged, while the system continues running.
Traditional imperative handling leads to boilerplate like this:
try {
const value = Number(req.query.value);
if (isNaN(value) || value < 0) throw new Error("Invalid value");
// business logic here
} catch (err) {
res.status(400).send("Malformed query");
}
Drawbacks:
- Validation is tangled with business logic
- Repetitive error handling
- Adding fields multiplies boilerplate
- Harder to reason about control flow
๐ Declarative Validation with ObjNavigator
ObjNavigator provides two fluent, composable methods for safe input handling:
coerce()— safely transform valuesvalidate()— assert correctness declaratively
They let you describe what must be true about input, not how to defensively process it.
Example
const nav = new ObjNavigator({ ...req.query })
.on('inputError', () => res.status(400).send('Malformed query')) // must be set first
.coerce('value', v => Number(v), { errorEvent: 'inputError' })
.validate('value', v => v >= 0, { errorEvent: 'inputError' });
if (nav.failed) return;
// ✅ Business logic here — safe, validated input
This reads as a clear specification of what happens, with business logic well separated from validation logic.
๐ How coerce() Works
nav.coerce('user.age', v => Number(v));
coerce():
- Resolves the value at the given path
- Applies a user-defined transformation
- Writes the coerced value back into the object
- Does not throw unless configured
- Marks the navigator as failed on error
Failure is sticky and explicit, making control flow predictable.
✅ How validate() Works
nav.validate('user.age', v => v >= 18);
validate():
- Reads the value at the path
- Executes a predicate
- Treats any falsy result as a validation failure
- Delegates error handling to the centralized
fail()mechanism
On failure, it can:
- Emit an event
- Bubble the event to parent navigators
- Invoke a callback
- Throw (if requested)
All without polluting business logic.
๐ Why Events Instead of Exceptions?
In asynchronous, distributed, or event-driven systems:
- WebSocket handlers have no caller to throw to
- Message queues require acknowledgment, not stack traces
- A single malformed message should not crash the consumer
Emitting events instead of throwing:
- Reports errors back to the sender
- Centralizes logging and metrics
- Emits different semantic events for different failures
- Keeps the system responsive
ObjNavigator extends EventEmitter, integrating naturally with Node.js infrastructures.
๐ Key Benefits
- Declarative — validation logic reads like a contract
- Chainable — multiple coercions and validations compose naturally
- Event-driven — ideal for async systems
- Fail-safe — no unsafe mutations after failure
- Listener-first — guarantees errors are handled predictably
⚡ Conclusion
With ObjNavigator, input handling is:
- Explicit instead of defensive
- Composable instead of repetitive
- Event-driven instead of exception-driven
Your handlers stay clean. Your business logic stays focused. Invalid input never sneaks through.
Comments
Post a Comment