From 255aee261ec33d253cd7fca82005b0b4f7f706f6 Mon Sep 17 00:00:00 2001 From: Ashfaqbs Date: Sun, 20 Sep 2026 20:27:01 +0530 Subject: [PATCH] fix: map HTTP 402 and 413 to status-specific exceptions Both fell through to the generic TypeSafeApiException, unlike every other documented status (400/401/403/404/422/429/5xx). Fixes #8. --- CHANGELOG.md | 4 ++++ .../premocloud/typesafe/TypeSafeApiException.java | 2 ++ .../typesafe/TypeSafePayloadTooLargeException.java | 13 +++++++++++++ .../typesafe/TypeSafePaymentRequiredException.java | 13 +++++++++++++ .../premocloud/typesafe/TypeSafeClientTest.java | 8 ++++++++ 5 files changed, 40 insertions(+) create mode 100644 typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePayloadTooLargeException.java create mode 100644 typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePaymentRequiredException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c82ba2e..1dbad2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- `TypeSafePaymentRequiredException` (402) and `TypeSafePayloadTooLargeException` (413) join the other status-specific exceptions; both statuses previously fell through to the generic `TypeSafeApiException` (#8). + ## 0.3.0 - 2026-09-19 - Async API: `systemOneAsync` mirroring every `systemOne` overload and `models().listAsync()` return `CompletableFuture`s driven by `HttpClient.sendAsync`, so retries back off without holding a thread, honoring the same per-call `RequestOptions` and failing with the same `TypeSafeException` subclasses as the blocking calls (#4). diff --git a/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafeApiException.java b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafeApiException.java index fe6e91f..d3049a6 100644 --- a/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafeApiException.java +++ b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafeApiException.java @@ -37,8 +37,10 @@ public static TypeSafeApiException fromResponse(int status, @Nullable String bod return switch (status) { case 400 -> new TypeSafeBadRequestException(status, body, headers); case 401 -> new TypeSafeAuthenticationException(status, body, headers); + case 402 -> new TypeSafePaymentRequiredException(status, body, headers); case 403 -> new TypeSafePermissionDeniedException(status, body, headers); case 404 -> new TypeSafeNotFoundException(status, body, headers); + case 413 -> new TypeSafePayloadTooLargeException(status, body, headers); case 422 -> new TypeSafeUnprocessableEntityException(status, body, headers); case 429 -> new TypeSafeRateLimitException(status, body, headers); default -> status >= 500 ? new TypeSafeInternalServerException(status, body, headers) : new TypeSafeApiException(status, body, headers); diff --git a/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePayloadTooLargeException.java b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePayloadTooLargeException.java new file mode 100644 index 0000000..a8e847f --- /dev/null +++ b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePayloadTooLargeException.java @@ -0,0 +1,13 @@ +package io.github.premocloud.typesafe; + +import org.jspecify.annotations.Nullable; + +import java.net.http.HttpHeaders; + +/** HTTP 413: the request body exceeded the API's size limit. */ +public class TypeSafePayloadTooLargeException extends TypeSafeApiException { + + TypeSafePayloadTooLargeException(int status, @Nullable String body, HttpHeaders headers) { + super(status, body, headers); + } +} diff --git a/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePaymentRequiredException.java b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePaymentRequiredException.java new file mode 100644 index 0000000..181e32e --- /dev/null +++ b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/TypeSafePaymentRequiredException.java @@ -0,0 +1,13 @@ +package io.github.premocloud.typesafe; + +import org.jspecify.annotations.Nullable; + +import java.net.http.HttpHeaders; + +/** HTTP 402: payment is required, e.g. the account is out of credits. */ +public class TypeSafePaymentRequiredException extends TypeSafeApiException { + + TypeSafePaymentRequiredException(int status, @Nullable String body, HttpHeaders headers) { + super(status, body, headers); + } +} diff --git a/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java b/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java index a7577f5..b706c4b 100644 --- a/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java +++ b/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java @@ -165,6 +165,14 @@ void errorsMapToStatusSpecificExceptionsWithExtractedMessages() { server.reply(400, ""); assertEquals("400 status code (no body)", assertThrows(TypeSafeBadRequestException.class, () -> client.systemOne(spamRequest())).getMessage()); + server.reply(402, "{\"error\":\"out of credits\"}"); + assertEquals("402 out of credits", + assertThrows(TypeSafePaymentRequiredException.class, () -> client.systemOne(spamRequest())).getMessage()); + + server.reply(413, "{\"error\":\"payload too large\"}"); + assertEquals("413 payload too large", + assertThrows(TypeSafePayloadTooLargeException.class, () -> client.systemOne(spamRequest())).getMessage()); + server.reply(503, "upstream down"); assertEquals("503 upstream down", assertThrows(TypeSafeInternalServerException.class, () -> client.systemOne(spamRequest())).getMessage());