Skip to content

Hata Yönetimi

@yildizpay/http-adapter, her ham hatayı — HTTP başarısızlıkları, OS seviyesindeki ağ hataları ve beklenmedik exception'lar — yapılandırılmış, tipli bir exception sınıfına dönüştürür. catch bloklarınızın ham durum kodlarını veya hata kodlarını manuel olarak incelemesine gerek kalmaz.

Exception Hiyerarşisi

BaseAdapterException
├── HttpException                    (herhangi bir HTTP yanıt hatası)
│   ├── 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
│   └── ... (tüm 4xx / 5xx kodları)
├── NetworkException                 (OS seviyesinde bağlantı hataları)
│   ├── ConnectionRefusedException   (ECONNREFUSED)  ← isRetryable() = true
│   ├── TimeoutException             (ETIMEDOUT / ECONNABORTED / AbortError)  ← isRetryable() = true
│   ├── SocketResetException         (ECONNRESET)  ← isRetryable() = true
│   ├── DnsResolutionException       (ENOTFOUND / EAI_AGAIN)
│   └── HostUnreachableException     (EHOSTUNREACH / ENETUNREACH)
├── ValidationException              (yanıt bir ResponseValidator'dan geçemedi)
├── UnknownException                 (sınıflandırılamayan herhangi bir hata)
└── CircuitBreakerOpenException      (devre açık, istek gönderilmedi)

Exception'ları Türüne Göre Yakalama

typescript
import {
  NotFoundException,
  TooManyRequestsException,
  TimeoutException,
  ConnectionRefusedException,
  CircuitBreakerOpenException,
  UnknownException,
} from '@yildizpay/http-adapter';

try {
  const response = await adapter.send<OdemeYaniti>(request);
} catch (error) {
  if (error instanceof NotFoundException) {
    // HTTP 404
    console.error('Kaynak bulunamadı:', error.response.data);

  } else if (error instanceof TooManyRequestsException) {
    // HTTP 429 — Retry-After başlık değerini kullan
    const retryAfterMs = error.getRetryAfterMs();
    console.warn(`İstek limiti aşıldı. ${retryAfterMs}ms sonra tekrar deneyin`);

  } else if (error instanceof TimeoutException) {
    // ETIMEDOUT / AbortError — downstream servis çok yavaş
    console.error('İstek zaman aşımına uğradı:', error.code);

  } else if (error instanceof ConnectionRefusedException) {
    // ECONNREFUSED — downstream servis çalışmıyor
    console.error('Servis çalışmıyor:', error.requestContext?.url);

  } else if (error instanceof CircuitBreakerOpenException) {
    // Devre açık — sunucuya istek atmadan hızlı başarısız ol
    console.error('Circuit breaker açık. İstek gönderilmiyor.');

  } else if (error instanceof UnknownException) {
    console.error('İşlenmeyen hata:', error.toJSON());
  }
}

Tip Koruyucular (Type Guards)

instanceof olmadan daralma tercih ediyorsanız — fonksiyonel pipeline'larda veya modül sınırlarını geçerken kullanışlıdır — her exception sınıfının bir tip koruyucusu mevcuttur:

typescript
import {
  isHttpException,
  isTimeoutException,
  isNetworkException,
  isCircuitBreakerOpenException,
} from '@yildizpay/http-adapter';

function handleError(error: unknown): void {
  if (isTimeoutException(error)) {
    // TypeScript artık biliyor: error, TimeoutException türünde
    retryIsleminiPlanla(error.requestContext?.url);
  } else if (isHttpException(error)) {
    // TypeScript artık biliyor: error, HttpException türünde
    izlemeRaporla(error.response.status, error.response.data);
  }
}

isRetryable() Sinyali

Her exception, isRetryable(): boolean metodunu sunar. Özel retry decorator'ları uygularken veya uygulama katmanında bir hatayı iletip iletmeme kararı verirken kullanışlıdır:

typescript
} catch (error) {
  if (error instanceof BaseAdapterException && error.isRetryable()) {
    return islemiYenidenDene();
  }
  throw error;
}

Yeniden denenebilir exception'lar: TooManyRequestsException (429), BadGatewayException (502), ServiceUnavailableException (503), GatewayTimeoutException (504), TimeoutException, SocketResetException, ConnectionRefusedException.

toJSON() ile Yapılandırılmış Loglama

Tüm exception'lar toJSON()'ı geçersiz kılar; bu sayede yapılandırılmış logger'larla (Pino, Winston vb.) uyumlu hale gelirler. JSON.stringify(error) düz bir Error'dan elde edeceğiniz boş {} yerine tam, iç içe bir nesne üretir:

typescript
} 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: 'Ödeme kaydı bulunamadı' },
    //     request: {
    //       method: 'GET',
    //       url: 'https://api.example.com/payments/123',
    //       correlationId: 'corr-abc'
    //     }
    //   }
    // }
  }
}

RequestContext — Güvenli İstek Meta Verisi

Her exception, kaynak istekten alınan method, url ve correlationId içeren bir RequestContext nesnesi taşır. Başlıklar ve gövde, loglarda auth token veya KKB sızıntısını önlemek için kasıtlı olarak hariç tutulmuştur.

typescript
} catch (error) {
  if (error instanceof NetworkException) {
    logger.warn({
      event: 'network_failure',
      exception: error.name,
      request: error.requestContext, // { method, url, correlationId }
    });
  }
}

Yanıt Doğrulayıcılar

HTTP çağrısı başarılı olduktan sonra, yanıt kodunuza ulaşmadan önce şema kısıtlamalarını veya iş kurallarını otomatik olarak uygulamak için isteklere doğrulayıcılar ekleyin. Doğrulayıcılar sırayla çalışır; ilk exception fırlatan doğrulayıcı zinciri durdurur.

typescript
import { ResponseValidator, ValidationException, Response } from '@yildizpay/http-adapter';

class OdemeDurumValidator implements ResponseValidator<IyzicoYaniti> {
  validate(response: Response<IyzicoYaniti>): void {
    if (response.data.status !== 'success') {
      throw new ValidationException(
        `Ödeme başarısız: ${response.data.errorMessage}`,
        response,
      );
    }
  }
}

// Herhangi bir şema doğrulama kütüphanesiyle çalışır — Zod, Joi vb.'ye sıfır bağımlılık
class OdemeSchemaValidator implements ResponseValidator<unknown> {
  validate(response: Response<unknown>): void {
    IyzicoYanitiSchema.parse(response.data); // Zod uyumsuzlukta exception fırlatır
  }
}

const request = new RequestBuilder('https://api.iyzipay.com')
  .setEndpoint('/payment/auth')
  .setMethod(HttpMethod.POST)
  .setBody(dto)
  .validateWith(new OdemeSchemaValidator(), new OdemeDurumValidator())
  .build();

Doğrulama başarısızlığını yakalamak:

typescript
import { isValidationException } from '@yildizpay/http-adapter';

} catch (error) {
  if (isValidationException(error)) {
    console.error('Doğrulama başarısız:', error.message);
    console.error('Ham yanıt:', error.response.data);
  }
}

Bir doğrulayıcı içinde fırlatılan BaseAdapterException dışındaki hatalar (örn. ZodError) otomatik olarak ValidationException içine sarılır; orijinal hata error.cause üzerinden erişilebilir:

typescript
} catch (error) {
  if (isValidationException<ZodError>(error) && error.cause) {
    console.error('Şema sorunları:', error.cause.issues);
  }
}

Released under the MIT License.