Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.4.0] - 2026-07-16

### Added

- Add `ApiErrorCodes`, grouped constants for documented API root codes and Facturapi-owned validation detail codes.
- Fix `FacturapiException.getErrorCode()` to return only documented string API root codes.

## [1.3.0] - 2026-06-07

### Added
Expand Down
9 changes: 6 additions & 3 deletions README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ Maven:
<dependency>
<groupId>io.facturapi</groupId>
<artifactId>facturapi-java</artifactId>
<version>1.1.0</version>
<version>1.4.0</version>
</dependency>
```

Gradle:

```gradle
implementation("io.facturapi:facturapi-java:1.1.0")
implementation("io.facturapi:facturapi-java:1.4.0")
```

## Inicio rápido
Expand Down Expand Up @@ -84,13 +84,16 @@ var updated = facturapi.organizations().uploadCertificate(

```java
import io.facturapi.FacturapiException;
import io.facturapi.constants.ApiErrorCodes;

try {
facturapi.customers().retrieve("cus_123");
} catch (FacturapiException e) {
System.out.println(e.getMessage());
System.out.println(e.getStatusCode());
System.out.println(e.getErrorCode());
if (ApiErrorCodes.RequestErrorCode.RATE_LIMIT_EXCEEDED.equals(e.getErrorCode())) {
System.out.println(e.getHeaders().get("retry-after"));
}
System.out.println(e.getErrorPath());
}
```
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ Maven:
<dependency>
<groupId>io.facturapi</groupId>
<artifactId>facturapi-java</artifactId>
<version>1.1.0</version>
<version>1.4.0</version>
</dependency>
```

Gradle:

```gradle
implementation("io.facturapi:facturapi-java:1.1.0")
implementation("io.facturapi:facturapi-java:1.4.0")
```

## Quickstart
Expand Down Expand Up @@ -84,13 +84,16 @@ var updated = facturapi.organizations().uploadCertificate(

```java
import io.facturapi.FacturapiException;
import io.facturapi.constants.ApiErrorCodes;

try {
facturapi.customers().retrieve("cus_123");
} catch (FacturapiException e) {
System.out.println(e.getMessage());
System.out.println(e.getStatusCode());
System.out.println(e.getErrorCode());
if (ApiErrorCodes.RequestErrorCode.RATE_LIMIT_EXCEEDED.equals(e.getErrorCode())) {
System.out.println(e.getHeaders().get("retry-after"));
}
System.out.println(e.getErrorPath());
}
```
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>io.facturapi</groupId>
<artifactId>facturapi-java</artifactId>
<version>1.3.0</version>
<version>1.4.0</version>
<name>facturapi-java</name>
<description>Official Java SDK for Facturapi</description>
<url>https://github.com/facturapi/facturapi-java</url>
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/io/facturapi/FacturapiException.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

public class FacturapiException extends RuntimeException {
private final int statusCode;
private final Object errorCode;
private final String errorCode;
private final String errorPath;
private final String errorLocation;
private final JsonNode errors;
Expand All @@ -29,14 +29,14 @@ public FacturapiException(String message, Throwable cause) {
this.headers = Collections.emptyMap();
}

public FacturapiException(String message, int statusCode, Object errorCode, String errorPath) {
public FacturapiException(String message, int statusCode, String errorCode, String errorPath) {
this(message, statusCode, errorCode, errorPath, null, null, null, Collections.emptyMap());
}

public FacturapiException(
String message,
int statusCode,
Object errorCode,
String errorCode,
String errorPath,
String errorLocation,
JsonNode errors,
Expand All @@ -57,7 +57,7 @@ public int getStatusCode() {
return statusCode;
}

public Object getErrorCode() {
public String getErrorCode() {
return errorCode;
}

Expand Down
285 changes: 285 additions & 0 deletions src/main/java/io/facturapi/constants/ApiErrorCodes.java

Large diffs are not rendered by default.

16 changes: 3 additions & 13 deletions src/main/java/io/facturapi/http/FacturapiHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private static JsonNode firstDefined(JsonNode node, String... keys) {
private FacturapiException buildApiException(String bodyText, Response response) {
int statusCode = response.code();
int resolvedStatus = statusCode;
Object errorCode = null;
String errorCode = null;
String errorPath = null;
String errorLocation = null;
JsonNode errors = null;
Expand Down Expand Up @@ -245,18 +245,8 @@ private FacturapiException buildApiException(String bodyText, Response response)
}

JsonNode codeNode = firstDefined(root, "code");
if (codeNode != null && !codeNode.isNull()) {
if (codeNode.isTextual()) {
errorCode = codeNode.asText();
} else if (codeNode.isIntegralNumber()) {
errorCode = codeNode.intValue();
} else if (codeNode.isNumber()) {
errorCode = codeNode.numberValue();
} else if (codeNode.isBoolean()) {
errorCode = codeNode.asBoolean();
} else {
errorCode = codeNode.toString();
}
if (codeNode != null && codeNode.isTextual()) {
errorCode = codeNode.asText();
}

JsonNode pathNode = firstDefined(root, "path");
Expand Down
25 changes: 23 additions & 2 deletions src/test/java/io/facturapi/FacturapiHttpClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.facturapi.constants.ApiErrorCodes;
import io.facturapi.http.FacturapiConfig;
import io.facturapi.http.FacturapiHttpClient;
import io.facturapi.models.GenericResponse;
Expand Down Expand Up @@ -52,7 +54,7 @@ void throwsFacturapiExceptionWithApiMessage() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(
400,
"{\"message\":\"Invalid customer\",\"status\":\"400\",\"code\":\"validation_error\",\"path\":\"customer.tax_id\",\"location\":\"body\",\"errors\":[{\"code\":\"required\",\"message\":\"tax id is required\",\"path\":\"customer.tax_id\",\"location\":\"body\"}]}",
"{\"message\":\"Invalid customer\",\"status\":\"400\",\"code\":\"invalid_request\",\"path\":\"customer.tax_id\",\"location\":\"body\",\"errors\":[{\"code\":\"required\",\"message\":\"tax id is required\",\"path\":\"customer.tax_id\",\"location\":\"body\"}]}",
Map.of("Retry-After", java.util.List.of("3"), "x-facturapi-log-id", java.util.List.of("log_123"))
);

Expand All @@ -69,12 +71,31 @@ void throwsFacturapiExceptionWithApiMessage() {

assertEquals(400, ex.getStatusCode());
assertTrue(ex.getMessage().contains("Invalid customer"));
assertEquals("validation_error", ex.getErrorCode());
assertEquals(ApiErrorCodes.RequestErrorCode.INVALID_REQUEST, ex.getErrorCode());
assertEquals("customer.tax_id", ex.getErrorPath());
assertEquals("body", ex.getErrorLocation());
assertEquals("log_123", ex.getLogId());
assertEquals("required", ex.getErrors().get(0).get("code").asText());
assertEquals("3", ex.getHeaders().get("Retry-After").get(0));
assertEquals("log_123", ex.getHeaders().get("x-facturapi-log-id").get(0));
}

@Test
void ignoresNonStringApiErrorCodes() {
StubHttpClient httpClient = new StubHttpClient();
httpClient.enqueueJson(400, "{\"message\":\"Invalid customer\",\"code\":400}");

FacturapiHttpClient client = new FacturapiHttpClient(
FacturapiConfig.builder("sk_test_123")
.httpClient(httpClient.client())
.build()
);

FacturapiException ex = assertThrows(
FacturapiException.class,
() -> client.get("/customers/cus_1", null, GenericResponse.class)
);

assertNull(ex.getErrorCode());
}
}
Loading