Refactor response handling and centralize JSON configuration
Replaced direct fields in `AbstractResponse` with `ResponseResultInfo` for better encapsulation and consistency. Introduced `JsonConf` to provide a shared, reusable `ObjectMapper` configuration. Updated tests and logic to align with the refactored response structure, enhancing maintainability and readability.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
/.factorypath
|
||||
/.springBeans
|
||||
/.idea
|
||||
*.iml
|
||||
|
||||
# local files
|
||||
/info.txt
|
||||
|
||||
@@ -2,14 +2,11 @@ package codes.thischwa.cf;
|
||||
|
||||
import codes.thischwa.cf.model.AbstractEntity;
|
||||
import codes.thischwa.cf.model.AbstractResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.hc.client5.http.classic.methods.HttpDelete;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPatch;
|
||||
@@ -49,53 +46,41 @@ abstract class CfBasicHttpClient {
|
||||
this.baseUrl = baseUrl;
|
||||
this.authEmail = authEmail;
|
||||
this.authKey = authKey;
|
||||
this.objectMapper = initObjectMapper();
|
||||
}
|
||||
|
||||
private ObjectMapper initObjectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||
return mapper;
|
||||
this.objectMapper = JsonConf.initObjectMapper();
|
||||
}
|
||||
|
||||
private CloseableHttpClient createHttpClient() {
|
||||
return HttpClients.custom()
|
||||
.addRequestInterceptorFirst(
|
||||
(request, context, execChain) -> {
|
||||
return HttpClients.custom().addRequestInterceptorFirst((request, context, execChain) -> {
|
||||
request.addHeader(HttpHeaders.ACCEPT_CHARSET, "UTF-8");
|
||||
request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
|
||||
request.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());
|
||||
request.addHeader(
|
||||
HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
|
||||
request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
|
||||
request.addHeader("X-Auth-Email", authEmail);
|
||||
request.addHeader("X-Auth-Key", authKey);
|
||||
})
|
||||
.build();
|
||||
}).build();
|
||||
}
|
||||
|
||||
private <T extends AbstractResponse> T executeRequest(
|
||||
ClassicHttpRequest request, Class<T> responseType) throws CloudflareApiException {
|
||||
private <T extends AbstractResponse> T executeRequest(ClassicHttpRequest request,
|
||||
Class<T> responseType)
|
||||
throws CloudflareApiException {
|
||||
String logUri = null;
|
||||
try (CloseableHttpClient client = createHttpClient()) {
|
||||
ResultWrapper result =
|
||||
client.execute(
|
||||
request,
|
||||
(ClassicHttpResponse response) ->
|
||||
new ResultWrapper(
|
||||
response.getCode(),
|
||||
ResultWrapper result = client.execute(request,
|
||||
(ClassicHttpResponse response) -> new ResultWrapper(response.getCode(),
|
||||
EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8)));
|
||||
|
||||
|
||||
logUri = request.getRequestUri();
|
||||
if (result.statusCode >= 200 && result.statusCode < 300) {
|
||||
return objectMapper.readValue(result.responseBody, responseType);
|
||||
T respObj = objectMapper.readValue(result.responseBody, responseType);
|
||||
if (!respObj.getResponseResultInfo().isSuccess()) {
|
||||
log.error("API error.");
|
||||
respObj.getResponseResultInfo().getErrors().forEach(e -> log.error(" - {}", e));
|
||||
throw new CloudflareApiException("API error.");
|
||||
}
|
||||
return respObj;
|
||||
} else {
|
||||
log.error(
|
||||
"{} request failed for URL {}: Status {}",
|
||||
request.getMethod(),
|
||||
request.getUri(),
|
||||
log.error("{} request failed for URL {}: Status {}", request.getMethod(), request.getUri(),
|
||||
result.statusCode);
|
||||
throw new CloudflareApiException(
|
||||
request.getMethod() + " request failed with status code: " + result.statusCode);
|
||||
@@ -104,55 +89,70 @@ abstract class CfBasicHttpClient {
|
||||
log.error("JSON parsing error for request to {}", logUri, e);
|
||||
throw new CloudflareApiException("Error processing JSON response", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Error during request execution", e);
|
||||
throw new CloudflareApiException("Request failed", e);
|
||||
throw new CloudflareApiException("Server error!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends a GET request to the given endpoint and maps the response. */
|
||||
/**
|
||||
* Sends a GET request to the given endpoint and maps the response.
|
||||
*/
|
||||
<T extends AbstractResponse> T getRequest(String endpoint, Class<T> responseType)
|
||||
throws CloudflareApiException {
|
||||
HttpGet request = new HttpGet(buildUrl(endpoint));
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/** Sends a DELETE request to the given endpoint and maps the response. */
|
||||
/**
|
||||
* Sends a DELETE request to the given endpoint and maps the response.
|
||||
*/
|
||||
<T extends AbstractResponse> T deleteRequest(String endpoint) throws CloudflareApiException {
|
||||
HttpDelete request = new HttpDelete(buildUrl(endpoint));
|
||||
return executeRequest(request, (Class<T>) codes.thischwa.cf.model.RecordSingleResponse.class);
|
||||
}
|
||||
|
||||
/** Sends a POST request with a payload to the given endpoint and maps the response. */
|
||||
<T extends AbstractResponse, R extends AbstractEntity> T postRequest(
|
||||
String endpoint, R requestPayload) throws CloudflareApiException {
|
||||
/**
|
||||
* Sends a POST request with a payload to the given endpoint and maps the response.
|
||||
*/
|
||||
<T extends AbstractResponse, R extends AbstractEntity> T postRequest(String endpoint,
|
||||
R requestPayload)
|
||||
throws CloudflareApiException {
|
||||
HttpPost request = new HttpPost(buildUrl(endpoint));
|
||||
setRequestPayload(request, requestPayload);
|
||||
return executeRequest(request, (Class<T>) codes.thischwa.cf.model.RecordSingleResponse.class);
|
||||
}
|
||||
|
||||
/** Sends a PUT request with a payload to the given endpoint and maps the response. */
|
||||
<T extends AbstractResponse, R extends AbstractEntity> T putRequest(
|
||||
String endpoint, R requestPayload, Class<T> responseType) throws CloudflareApiException {
|
||||
/**
|
||||
* Sends a PUT request with a payload to the given endpoint and maps the response.
|
||||
*/
|
||||
<T extends AbstractResponse, R extends AbstractEntity> T putRequest(String endpoint,
|
||||
R requestPayload,
|
||||
Class<T> responseType)
|
||||
throws CloudflareApiException {
|
||||
HttpPut request = new HttpPut(buildUrl(endpoint));
|
||||
setRequestPayload(request, requestPayload);
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/** Sends a PATCH request with a payload to the given endpoint and maps the response. */
|
||||
<T extends AbstractResponse, R extends AbstractEntity> T patchRequest(
|
||||
String endpoint, R requestPayload) throws CloudflareApiException {
|
||||
/**
|
||||
* Sends a PATCH request with a payload to the given endpoint and maps the response.
|
||||
*/
|
||||
<T extends AbstractResponse, R extends AbstractEntity> T patchRequest(String endpoint,
|
||||
R requestPayload)
|
||||
throws CloudflareApiException {
|
||||
HttpPatch request = new HttpPatch(buildUrl(endpoint));
|
||||
setRequestPayload(request, requestPayload);
|
||||
return executeRequest(request, (Class<T>) codes.thischwa.cf.model.RecordSingleResponse.class);
|
||||
}
|
||||
|
||||
/** Sets the JSON payload for a request. */
|
||||
private <R extends AbstractEntity> void setRequestPayload(
|
||||
BasicClassicHttpRequest request, R requestPayload) throws CloudflareApiException {
|
||||
/**
|
||||
* Sets the JSON payload for a request.
|
||||
*/
|
||||
private <R extends AbstractEntity> void setRequestPayload(BasicClassicHttpRequest request,
|
||||
R requestPayload)
|
||||
throws CloudflareApiException {
|
||||
try {
|
||||
request.setEntity(
|
||||
new StringEntity(
|
||||
objectMapper.writeValueAsString(requestPayload), ContentType.APPLICATION_JSON));
|
||||
request.setEntity(new StringEntity(objectMapper.writeValueAsString(requestPayload),
|
||||
ContentType.APPLICATION_JSON));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new CloudflareApiException("Error serializing JSON payload", e);
|
||||
}
|
||||
@@ -162,5 +162,6 @@ abstract class CfBasicHttpClient {
|
||||
return baseUrl + endpoint;
|
||||
}
|
||||
|
||||
private record ResultWrapper(int statusCode, String responseBody) {}
|
||||
private record ResultWrapper(int statusCode, String responseBody) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import codes.thischwa.cf.model.RecordEntity;
|
||||
import codes.thischwa.cf.model.RecordMultipleResponse;
|
||||
import codes.thischwa.cf.model.RecordSingleResponse;
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import codes.thischwa.cf.model.ResponseResultInfo;
|
||||
import codes.thischwa.cf.model.ZoneEntity;
|
||||
import codes.thischwa.cf.model.ZoneMultipleResponse;
|
||||
import java.util.List;
|
||||
@@ -307,9 +308,10 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
|
||||
private void checkResponse(AbstractResponse resp, boolean singleResultExpected)
|
||||
throws CloudflareApiException {
|
||||
if (!resp.isSuccess()) {
|
||||
ResponseResultInfo resultInfo = resp.getResponseResultInfo();
|
||||
if (!resultInfo.isSuccess()) {
|
||||
String errors =
|
||||
resp.getErrors().stream().map(Object::toString).collect(Collectors.joining(", "));
|
||||
resultInfo.getErrors().stream().map(Object::toString).collect(Collectors.joining(", "));
|
||||
throw new CloudflareApiException("Error in response: " + errors);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
/**
|
||||
* The JsonConf class provides a utility method for initializing and configuring a shared
|
||||
* {@link ObjectMapper} instance for JSON serialization and deserialization.
|
||||
*/
|
||||
class JsonConf {
|
||||
|
||||
static ObjectMapper initObjectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public abstract class AbstractResponse {
|
||||
private boolean success;
|
||||
private List<String> errors;
|
||||
private List<String> messages;
|
||||
|
||||
@JsonUnwrapped
|
||||
private ResponseResultInfo responseResultInfo;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Represents the result of a response with metadata about its success and associated messages or
|
||||
* errors.
|
||||
* <p>This class provides a structure to capture the outcome of an operation, including:
|
||||
* <ul>
|
||||
* <li>Whether the operation was successful.
|
||||
* <li>A list of error messages if the operation failed.
|
||||
* <li>A list of informational or success messages.
|
||||
* </ul>
|
||||
* It can be used to standardize the response format in an application.
|
||||
*/
|
||||
@Data
|
||||
public class ResponseResultInfo {
|
||||
private boolean success;
|
||||
private List<String> errors;
|
||||
private List<String> messages;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import codes.thischwa.cf.model.ZoneMultipleResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ObjectMapperTest {
|
||||
|
||||
@Test
|
||||
void testObjectMapper() throws IOException {
|
||||
ObjectMapper mapper = JsonConf.initObjectMapper();
|
||||
ZoneMultipleResponse resp =
|
||||
mapper.readValue(this.getClass().getResourceAsStream("/zone-list-response.json"),
|
||||
ZoneMultipleResponse.class);
|
||||
assertNotNull(resp.getResponseResultInfo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"result": null,
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": 81053,
|
||||
"message": "An A, AAAA, or CNAME record with that host already exists. For more details, refer to \u003chttps://developers.cloudflare.com/dns/manage-dns-records/troubleshooting/records-with-same-name/\u003e."
|
||||
}
|
||||
],
|
||||
"messages": []
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="true">
|
||||
<configuration>
|
||||
|
||||
<appender name="current"
|
||||
class="ch.qos.logback.core.ConsoleAppender">
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"result": [
|
||||
{
|
||||
"id": "0a83dd6e7f8c46039f2517bbded8115e",
|
||||
"name": "mein-d-ns.de",
|
||||
"status": "active",
|
||||
"paused": false,
|
||||
"type": "full",
|
||||
"development_mode": -230181,
|
||||
"name_servers": [
|
||||
"blair.ns.cloudflare.com",
|
||||
"sergi.ns.cloudflare.com"
|
||||
],
|
||||
"original_name_servers": [
|
||||
"c.ns14.net",
|
||||
"b.ns14.net",
|
||||
"a.ns14.net",
|
||||
"d.ns14.net"
|
||||
],
|
||||
"original_registrar": null,
|
||||
"original_dnshost": null,
|
||||
"modified_on": "2025-01-20T09:06:52.122538Z",
|
||||
"created_on": "2025-01-20T08:56:34.736214Z",
|
||||
"activated_on": "2025-01-20T09:06:52.122538Z",
|
||||
"meta": {
|
||||
"step": 2,
|
||||
"custom_certificate_quota": 0,
|
||||
"page_rule_quota": 3,
|
||||
"phishing_detected": false
|
||||
},
|
||||
"owner": {
|
||||
"id": null,
|
||||
"type": "user",
|
||||
"email": null
|
||||
},
|
||||
"account": {
|
||||
"id": "563335e3d73549dbbc0620f4ce82d527",
|
||||
"name": "myAccount"
|
||||
},
|
||||
"tenant": {
|
||||
"id": null,
|
||||
"name": null
|
||||
},
|
||||
"tenant_unit": {
|
||||
"id": null
|
||||
},
|
||||
"permissions": [
|
||||
"#image:read",
|
||||
"#image:edit",
|
||||
"#worker:edit",
|
||||
"#analytics:read",
|
||||
"#ssl:edit",
|
||||
"#zone_settings:read",
|
||||
"#organization:edit",
|
||||
"#waf:read",
|
||||
"#waf:edit",
|
||||
"#logs:read",
|
||||
"#member:read",
|
||||
"#worker:read",
|
||||
"#blocks:read",
|
||||
"#blocks:edit",
|
||||
"#access:read",
|
||||
"#access:edit",
|
||||
"#billing:read",
|
||||
"#teams:read",
|
||||
"#organization:read",
|
||||
"#logs:edit",
|
||||
"#zaraz:publish",
|
||||
"#zone_settings:edit",
|
||||
"#fbm:read",
|
||||
"#subscription:read",
|
||||
"#lb:read",
|
||||
"#waitingroom:edit",
|
||||
"#ssl:read",
|
||||
"#subscription:edit",
|
||||
"#dns_records:read",
|
||||
"#dns_records:edit",
|
||||
"#api_gateway:read",
|
||||
"#api_gateway:edit",
|
||||
"#cds_compute_account:edit",
|
||||
"#ces_submissions:read",
|
||||
"#fbm:edit",
|
||||
"#healthchecks:edit",
|
||||
"#stream:edit",
|
||||
"#lb:edit",
|
||||
"#healthchecks:read",
|
||||
"#billing:edit",
|
||||
"#member:edit",
|
||||
"#cfone_read",
|
||||
"#cfone_edit",
|
||||
"#integration:read",
|
||||
"#zaraz:edit",
|
||||
"#magic:read",
|
||||
"#magic:edit",
|
||||
"#zaraz:read",
|
||||
"#stream:read",
|
||||
"#integration:install",
|
||||
"#dash_sso:edit",
|
||||
"#zone:read",
|
||||
"#http_applications:read",
|
||||
"#http_applications:edit",
|
||||
"#ces_analytics:read",
|
||||
"#resilience:edit",
|
||||
"#teams:edit",
|
||||
"#r2_bucket_warehouse:read",
|
||||
"#r2_bucket_warehouse:edit",
|
||||
"#query_cache:read",
|
||||
"#query_cache:edit",
|
||||
"#dex:read",
|
||||
"#zone_versioning:read",
|
||||
"#zone_versioning:edit",
|
||||
"#cds:read",
|
||||
"#ces_search:action",
|
||||
"#ces_search:read",
|
||||
"#ces_search:preview",
|
||||
"#ces_search:raw",
|
||||
"#ces_search:trace",
|
||||
"#fbm_acc:edit",
|
||||
"#teams:pii",
|
||||
"#r2_bucket_item:read",
|
||||
"#r2_bucket_item:edit",
|
||||
"#integration:edit",
|
||||
"#resilience:read",
|
||||
"#zone:edit",
|
||||
"#dash_sso:read",
|
||||
"#legal:read",
|
||||
"#ces_integration:edit",
|
||||
"#ces_integration:read",
|
||||
"#cache_purge:edit",
|
||||
"#vectorize:read",
|
||||
"#vectorize:edit",
|
||||
"#auditlogs:read",
|
||||
"#teams:report",
|
||||
"#ces_settings:edit",
|
||||
"#ces_settings:read",
|
||||
"#waitingroom:read",
|
||||
"#web3:read",
|
||||
"#web3:edit",
|
||||
"#dex:edit",
|
||||
"#cds_compute_account:read",
|
||||
"#cds:edit",
|
||||
"#r2_bucket:read",
|
||||
"#r2_bucket:edit",
|
||||
"#ces_phishguard:read",
|
||||
"#page_shield:read",
|
||||
"#page_shield:edit",
|
||||
"#legal:edit",
|
||||
"#app:edit"
|
||||
],
|
||||
"plan": {
|
||||
"id": "0feeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"name": "Free Website",
|
||||
"price": 0,
|
||||
"currency": "USD",
|
||||
"frequency": "",
|
||||
"is_subscribed": false,
|
||||
"can_subscribe": false,
|
||||
"legacy_id": "free",
|
||||
"legacy_discount": false,
|
||||
"externally_managed": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"result_info": {
|
||||
"page": 1,
|
||||
"per_page": 20,
|
||||
"total_pages": 1,
|
||||
"count": 1,
|
||||
"total_count": 1
|
||||
},
|
||||
"success": true,
|
||||
"errors": [],
|
||||
"messages": []
|
||||
}
|
||||
Reference in New Issue
Block a user