Skip to content

Commit d743b5d

Browse files
Expose typed ErrorCode on ApiException and Error alongside raw code
1 parent 2e5cb83 commit d743b5d

7 files changed

Lines changed: 161 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99
### Added
10+
- Typed error codes: `ApiException#getErrorCode()` (and `Error#getErrorCode()`) now return a strongly typed `ErrorCode` enum in addition to the raw string `code`, allowing callers to branch on error conditions without string matching. Unrecognized codes return `null` while the raw code remains available.
1011
- Automatic retries with exponential backoff for transient network errors and 5xx server responses, configurable via `IpregistryConfig` (`retryMaxAttempts`, `retryInterval`, `retryOnServerError`, `retryOnTooManyRequests`). Retries on 429 Too Many Requests are disabled by default and honor the `Retry-After` header when enabled.
1112
- `IpregistryClient` now implements `Closeable` to release the underlying HTTP connection pool.
1213

‎README.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,23 @@ Main subtypes are _ApiException_ and _ClientException_.
165165

166166
Exceptions of type _ApiException_ include a code field that maps to the one described in the [Ipregistry documentation](https://ipregistry.co/docs/errors).
167167

168+
In addition to the raw string `code`, an _ApiException_ exposes a typed `ErrorCode` via `getErrorCode()`, so you can branch on error conditions without string matching:
169+
170+
```java
171+
try {
172+
IpInfo info = ipregistry.lookup("8.8.8.8");
173+
} catch (ApiException e) {
174+
ErrorCode code = e.getErrorCode(); // null if the raw code is not recognized
175+
if (code == ErrorCode.INSUFFICIENT_CREDITS) {
176+
// handle exhausted credits
177+
} else if (code == ErrorCode.TOO_MANY_REQUESTS) {
178+
// handle rate limiting
179+
}
180+
} catch (ClientException e) {
181+
// handle client-side/network error
182+
}
183+
```
184+
168185
## Filtering bots
169186

170187
You might want to prevent Ipregistry API calls for crawlers or bots browsing your pages.

‎src/main/java/co/ipregistry/api/client/exceptions/ApiException.java‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package co.ipregistry.api.client.exceptions;
1818

19+
import co.ipregistry.api.client.model.error.ErrorCode;
1920
import lombok.Getter;
2021

2122

@@ -26,10 +27,16 @@
2627
public class ApiException extends IpregistryException {
2728

2829
/**
29-
* The API exception code.
30+
* The raw API exception code as returned by the API.
3031
*/
3132
private final String code;
3233

34+
/**
35+
* The typed API error code, or {@code null} when {@link #code} does not match any known
36+
* {@link ErrorCode} (for instance a code introduced after this client release).
37+
*/
38+
private final ErrorCode errorCode;
39+
3340
/**
3441
* The API exception message.
3542
*/
@@ -44,15 +51,16 @@ public class ApiException extends IpregistryException {
4451
/**
4552
* Creates a new instance.
4653
*
47-
* @param errorCode an error code as described in our docs.
54+
* @param code an error code as described in our docs.
4855
* @param message a message describing the error.
4956
* @param resolution explanation about how to fix the error.
5057
*
5158
* @see <a href="https://ipregistry.co/docs/errors">https://ipregistry.co/docs/errors</a>.
5259
*/
53-
public ApiException(final String errorCode, final String message, final String resolution) {
60+
public ApiException(final String code, final String message, final String resolution) {
5461
super(message);
55-
this.code = errorCode;
62+
this.code = code;
63+
this.errorCode = ErrorCode.fromCode(code);
5664
this.message = message;
5765
this.resolution = resolution;
5866
}

‎src/main/java/co/ipregistry/api/client/model/error/Error.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,14 @@ public Error() {
6060
@JsonProperty("resolution")
6161
private String resolution;
6262

63+
/**
64+
* Returns the typed {@link ErrorCode} for this error.
65+
*
66+
* @return the {@link ErrorCode} matching {@link #getCode()}, or {@code null} if the code is
67+
* {@code null} or not recognized.
68+
*/
69+
public ErrorCode getErrorCode() {
70+
return ErrorCode.fromCode(code);
71+
}
72+
6373
}

‎src/main/java/co/ipregistry/api/client/model/error/ErrorCode.java‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package co.ipregistry.api.client.model.error;
1818

19+
import java.util.Locale;
20+
1921

2022
/**
2123
* Enumerates error codes.
@@ -99,6 +101,26 @@ public enum ErrorCode {
99101
/**
100102
* No Autonomous System is currently announcing the specified IP address or IP prefix. This can happen with the Origin AS endpoint.
101103
*/
102-
UNKNOWN_ASN
104+
UNKNOWN_ASN;
105+
106+
/**
107+
* Returns the {@link ErrorCode} matching the specified raw API error code.
108+
*
109+
* @param code the raw error code as returned by the API (e.g. {@code "TOO_MANY_REQUESTS"}).
110+
* @return the matching {@link ErrorCode}, or {@code null} if {@code code} is {@code null} or
111+
* does not correspond to any known error code (for instance a code introduced after this
112+
* client release).
113+
*/
114+
public static ErrorCode fromCode(final String code) {
115+
if (code == null) {
116+
return null;
117+
}
118+
119+
try {
120+
return ErrorCode.valueOf(code.trim().toUpperCase(Locale.ROOT));
121+
} catch (final IllegalArgumentException e) {
122+
return null;
123+
}
124+
}
103125

104126
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2019 Ipregistry (https://ipregistry.co).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package co.ipregistry.api.client.exceptions;
18+
19+
import co.ipregistry.api.client.model.error.ErrorCode;
20+
import org.junit.jupiter.api.Assertions;
21+
import org.junit.jupiter.api.Test;
22+
23+
class ApiExceptionTest {
24+
25+
@Test
26+
void testExposesTypedErrorCodeForKnownCode() {
27+
final ApiException exception =
28+
new ApiException("INSUFFICIENT_CREDITS", "No credits left", "Buy credits");
29+
30+
Assertions.assertEquals("INSUFFICIENT_CREDITS", exception.getCode());
31+
Assertions.assertEquals(ErrorCode.INSUFFICIENT_CREDITS, exception.getErrorCode());
32+
}
33+
34+
@Test
35+
void testKeepsRawCodeButNullTypedCodeForUnknownCode() {
36+
final ApiException exception =
37+
new ApiException("A_BRAND_NEW_CODE", "message", "resolution");
38+
39+
Assertions.assertEquals("A_BRAND_NEW_CODE", exception.getCode());
40+
Assertions.assertNull(exception.getErrorCode());
41+
}
42+
43+
@Test
44+
void testSubclassesInheritTypedErrorCode() {
45+
final ApiException exception =
46+
new IpInfoException("TOO_MANY_REQUESTS", "Slow down", "Retry later");
47+
48+
Assertions.assertEquals(ErrorCode.TOO_MANY_REQUESTS, exception.getErrorCode());
49+
}
50+
51+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2019 Ipregistry (https://ipregistry.co).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package co.ipregistry.api.client.model.error;
18+
19+
import org.junit.jupiter.api.Assertions;
20+
import org.junit.jupiter.api.Test;
21+
22+
class ErrorCodeTest {
23+
24+
@Test
25+
void testFromCodeMapsKnownCodes() {
26+
Assertions.assertEquals(ErrorCode.TOO_MANY_REQUESTS, ErrorCode.fromCode("TOO_MANY_REQUESTS"));
27+
Assertions.assertEquals(ErrorCode.INVALID_API_KEY, ErrorCode.fromCode("INVALID_API_KEY"));
28+
Assertions.assertEquals(ErrorCode.INSUFFICIENT_CREDITS, ErrorCode.fromCode("INSUFFICIENT_CREDITS"));
29+
}
30+
31+
@Test
32+
void testFromCodeIsCaseInsensitiveAndTrimmed() {
33+
Assertions.assertEquals(ErrorCode.BAD_REQUEST, ErrorCode.fromCode(" bad_request "));
34+
}
35+
36+
@Test
37+
void testFromCodeReturnsNullForUnknownCode() {
38+
Assertions.assertNull(ErrorCode.fromCode("A_BRAND_NEW_CODE"));
39+
Assertions.assertNull(ErrorCode.fromCode(""));
40+
}
41+
42+
@Test
43+
void testFromCodeReturnsNullForNull() {
44+
Assertions.assertNull(ErrorCode.fromCode(null));
45+
}
46+
47+
}

0 commit comments

Comments
 (0)