Error Handling
@yildizpay/http-adapter converts every raw error — HTTP failures, OS-level network errors, and unexpected exceptions — into a structured, typed exception class. Your catch blocks never need to inspect raw status codes or error codes manually.
Exception Hierarchy
BaseAdapterException
├── HttpException (any HTTP response error)
│ ├── BadRequestException (400)
│ ├── UnauthorizedException (401)
│ ├── ForbiddenException (403)
│ ├── NotFoundException (404)
│ ├── ConflictException (409)
│ ├── UnprocessableEntityException (422)
│ ├── TooManyRequestsException (429) ← isRetryable() = true
│ ├── InternalServerErrorException (500)
│ ├── BadGatewayException (502) ← isRetryable() = true
│ ├── ServiceUnavailableException (503) ← isRetryable() = true
│ ├── GatewayTimeoutException (504) ← isRetryable() = true
│ └── ... (all 4xx / 5xx codes)
├── NetworkException (OS-level connectivity failures)
│ ├── ConnectionRefusedException (ECONNREFUSED) ← isRetryable() = true
│ ├── TimeoutException (ETIMEDOUT / ECONNABORTED / AbortError) ← isRetryable() = true
│ ├── SocketResetException (ECONNRESET) ← isRetryable() = true
│ ├── DnsResolutionException (ENOTFOUND / EAI_AGAIN)
│ └── HostUnreachableException (EHOSTUNREACH / ENETUNREACH)
├── ValidationException (response failed a ResponseValidator)
├── UnknownException (any unclassifiable error)
└── CircuitBreakerOpenException (circuit is open, request not sent)Catching Exceptions by Type
import {
NotFoundException,
TooManyRequestsException,
TimeoutException,
ConnectionRefusedException,
CircuitBreakerOpenException,
UnknownException,
} from '@yildizpay/http-adapter';
try {
const response = await adapter.send<PaymentResponse>(request);
} catch (error) {
if (error instanceof NotFoundException) {
// HTTP 404
console.error('Resource not found:', error.response.data);
} else if (error instanceof TooManyRequestsException) {
// HTTP 429 — use the Retry-After header value if present
const retryAfterMs = error.getRetryAfterMs();
console.warn(`Rate limited. Retry after ${retryAfterMs}ms`);
} else if (error instanceof TimeoutException) {
// ETIMEDOUT / AbortError — downstream service too slow
console.error('Request timed out:', error.code);
} else if (error instanceof ConnectionRefusedException) {
// ECONNREFUSED — downstream service is down
console.error('Service is down:', error.requestContext?.url);
} else if (error instanceof CircuitBreakerOpenException) {
// Circuit is open — fail fast without hitting the server
console.error('Circuit breaker is open. Not sending request.');
} else if (error instanceof UnknownException) {
console.error('Unhandled error:', error.toJSON());
}
}Type Guards
If you prefer narrowing without instanceof — useful in functional pipelines or when crossing module boundaries — every exception class has a corresponding type guard:
import {
isHttpException,
isTimeoutException,
isConnectionRefusedException,
isCircuitBreakerOpenException,
isNetworkException,
} from '@yildizpay/http-adapter';
function handleError(error: unknown): void {
if (isTimeoutException(error)) {
// TypeScript now knows: error is TimeoutException
scheduleRetry(error.requestContext?.url);
} else if (isHttpException(error)) {
// TypeScript now knows: error is HttpException
reportToMonitoring(error.response.status, error.response.data);
}
}isRetryable() Signal
Every exception exposes isRetryable(): boolean — useful when implementing custom retry decorators or deciding at the application layer whether to propagate or retry:
} catch (error) {
if (error instanceof BaseAdapterException && error.isRetryable()) {
return retryOperation();
}
throw error;
}Retryable exceptions: TooManyRequestsException (429), BadGatewayException (502), ServiceUnavailableException (503), GatewayTimeoutException (504), TimeoutException, SocketResetException, ConnectionRefusedException.
Structured Logging with toJSON()
All exceptions override toJSON(), making them compatible with structured loggers (Pino, Winston, etc.). JSON.stringify(error) produces a complete, nested object — not the empty {} you'd get from a plain Error:
} catch (error) {
if (error instanceof BaseAdapterException) {
logger.error(error.toJSON());
// {
// name: 'NotFoundException',
// message: 'Not Found',
// code: 'ERR_NOT_FOUND',
// stack: '...',
// response: {
// status: 404,
// data: { detail: 'Payment record not found' },
// request: {
// method: 'GET',
// url: 'https://api.example.com/payments/123',
// correlationId: 'corr-abc'
// }
// }
// }
}
}RequestContext — Safe Request Metadata
Every exception carries a RequestContext object with method, url, and correlationId sourced from the originating request. Headers and body are deliberately excluded to prevent accidental auth-token or PII leakage in logs.
} catch (error) {
if (error instanceof NetworkException) {
logger.warn({
event: 'network_failure',
exception: error.name,
request: error.requestContext, // { method, url, correlationId }
});
}
}Response Validators
Attach validators to a request to enforce schema constraints or business rules on the response before it reaches your code. Validators run sequentially after the HTTP call succeeds. The first validator that throws halts the chain.
import { ResponseValidator, ValidationException, Response } from '@yildizpay/http-adapter';
class PaymentStatusValidator implements ResponseValidator<IyzicoResponse> {
validate(response: Response<IyzicoResponse>): void {
if (response.data.status !== 'success') {
throw new ValidationException(
`Payment failed: ${response.data.errorMessage}`,
response,
);
}
}
}
// Works with any schema validation library — zero coupling to Zod, Joi, etc.
class PaymentSchemaValidator implements ResponseValidator<unknown> {
validate(response: Response<unknown>): void {
IyzicoResponseSchema.parse(response.data); // Zod throws on mismatch
}
}
const request = new RequestBuilder('https://api.iyzipay.com')
.setEndpoint('/payment/auth')
.setMethod(HttpMethod.POST)
.setBody(dto)
.validateWith(new PaymentSchemaValidator(), new PaymentStatusValidator())
.build();Catching a validation failure:
import { isValidationException } from '@yildizpay/http-adapter';
} catch (error) {
if (isValidationException(error)) {
console.error('Validation failed:', error.message);
console.error('Raw response:', error.response.data);
}
}Non-BaseAdapterException errors thrown inside a validator (e.g., ZodError) are automatically wrapped in ValidationException with the original error available as error.cause:
} catch (error) {
if (isValidationException<ZodError>(error) && error.cause) {
console.error('Schema issues:', error.cause.issues);
}
}