๐Ÿ›ก️ 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:

  1. Coerced into runtime types (numbers, booleans, dates, etc.)
  2. Validated against business rules
  3. 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 values
  • validate() — 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():

  1. Resolves the value at the given path
  2. Applies a user-defined transformation
  3. Writes the coerced value back into the object
  4. Does not throw unless configured
  5. 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():

  1. Reads the value at the path
  2. Executes a predicate
  3. Treats any falsy result as a validation failure
  4. 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

Popular posts from this blog

๐ŸŒ Plain JavaScript Objects — the True Language of Distributed Systems

๐Ÿงฑ v0.0.0-dev.1 — First Brick

๐Ÿฆ v0.0.0-dev.2 — Sweet Refinements