Files
2026-05-27 23:29:53 +01:00

4.5 KiB

Error codes — @suaveplan/sms-providers

(no submodules)

Purpose

Defines the stable, namespaced error-code registry SMS_PROVIDERS_CODES and the SmsProvidersError discriminated subclass used by every public surface of @suaveplan/sms-providers. Every failure path the package can take — a non-2xx HTTP response from a provider, an upstream request timeout, a JSON parse failure on a response body, a missing configuration value, a webhook signature mismatch, an invalid E.164 number, a registry name collision, a missing registry lookup — raises one of these eight codes so downstream loggers, problem+json responders, retry wrappers, and analytics pipelines see a consistent shape.

Codes are minted through @suaveplan/error's defineCodes("SMS_PROVIDERS", […]) so they sit in a non-colliding namespace (SMS_PROVIDERS.*) distinct from @suaveplan/sms's SMS.* codes. The SmsProvidersError class extends SuaveplanError<SmsProvidersErrorCode> so instanceof SmsProvidersError narrows error.code to the union; optional provider, httpStatus, url, and bodyExcerpt discriminants are carried so consumers can branch without re-parsing the message.

Features

  • Eight codes covering every public-surface failure path.
  • Six factory helpers (httpError, timeoutError, parseError, configMissing, signatureInvalid, invalidNumber, providerNotRegistered, providerAlreadyRegistered).
  • Response bodies are truncated to 256 chars on the bodyExcerpt discriminant — bounded log volume regardless of payload size.
  • cause is preserved for any wrapped exception (timeout, parse failure) so root-cause traversal still works.

Usage: Basic

import {
    SmsProvidersError,
    SMS_PROVIDERS_CODES,
    httpError,
} from "@suaveplan/sms-providers";

try {
    /* call provider */
} catch (err) {
    if (
        err instanceof SmsProvidersError &&
        err.code === SMS_PROVIDERS_CODES.HTTP_ERROR
    ) {
        console.warn(`HTTP ${err.httpStatus} from ${err.provider}`);
    }
}

const e = httpError("twilio", 422, "https://api.twilio.com/x", "{}", "invalid");

Usage: Advanced

import {
    SmsProvidersError,
    SMS_PROVIDERS_CODES,
    signatureInvalid,
} from "@suaveplan/sms-providers";

// A custom webhook router that maps `SMS_PROVIDERS.SIGNATURE_INVALID` to a 401.
export function verifyOrThrow(
    provider: string,
    ok: boolean,
    reason: string
): void {
    if (!ok) {
        throw signatureInvalid(provider, reason);
    }
}

// Branch on every code for a problem+json renderer.
function statusFor(err: SmsProvidersError): number {
    switch (err.code) {
        case SMS_PROVIDERS_CODES.SIGNATURE_INVALID:
            return 401;
        case SMS_PROVIDERS_CODES.CONFIG_MISSING:
            return 500;
        case SMS_PROVIDERS_CODES.INVALID_NUMBER:
            return 400;
        case SMS_PROVIDERS_CODES.PROVIDER_NOT_REGISTERED:
            return 404;
        case SMS_PROVIDERS_CODES.PROVIDER_ALREADY_REGISTERED:
            return 409;
        case SMS_PROVIDERS_CODES.TIMEOUT:
        case SMS_PROVIDERS_CODES.HTTP_ERROR:
        case SMS_PROVIDERS_CODES.PARSE_ERROR:
            return err.httpStatus ?? 502;
        default:
            return 500;
    }
}

API Reference

Symbol Kind Notes
SMS_PROVIDERS_CODES const record Frozen map produced by defineCodes("SMS_PROVIDERS", […])
SmsProvidersErrorCode type Union of every code value
SmsProvidersError class Extends SuaveplanError with optional provider / httpStatus / url / bodyExcerpt
httpError(provider, status, url, body, message?) factory HTTP_ERROR
timeoutError(provider, url, timeoutMs, cause?) factory TIMEOUT
parseError(provider, url, body, cause) factory PARSE_ERROR
configMissing(provider, key) factory CONFIG_MISSING
signatureInvalid(provider, reason) factory SIGNATURE_INVALID
invalidNumber(provider, number) factory INVALID_NUMBER
providerNotRegistered(name) factory PROVIDER_NOT_REGISTERED
providerAlreadyRegistered(name) factory PROVIDER_ALREADY_REGISTERED

Implementation Notes

bodyExcerpt truncation keeps the elision sentinel … (U+2026) — distinguishable from a literal three-dot ASCII triplet so log scrapers can tell when truncation fired. The factories never construct errors from raw Error; they always go through new SmsProvidersError(...) so the prototype chain stays intact under transpiler-emitted __extends shims.