issue #9:
Change default behavior of `emptyResultThrowsException` to `false`, update `ResponseValidator` for improved handling of single and multiple results, and enhance test coverage and documentation. Add Fluent API for DNS operations, update `README`, and refactor `CfDnsClient` for zone-level chaining. Validate record type in `RecordEntity#build`, throw exception for invalid types, and add test coverage. Refactor `RecordEntity#getSld` to handle `zoneName` cases, improve null safety, and enhance substring logic. Refactor `RecordEntity` getter (`getName` → `getSld`) for clarity, improve string handling in batch processing, and enhance type safety in HTTP operations. Update tests and documentation accordingly. Refactor `deleteRequest` and `patchRequest` to accept `responseType` parameter for improved flexibility. Extract methods for cleaning and processing batch DNS records. Refactor API method names for consistency (`zoneListAll` → `zoneList`, `zoneInfo` → `zoneGet`, `sldListAll` → `recordList`, `sldInfo` → `recordGet`) and update related documentation and tests.
This commit is contained in:
@@ -102,10 +102,16 @@ abstract class CfBasicHttpClient {
|
||||
|
||||
/**
|
||||
* Sends a DELETE request to the given endpoint and maps the response.
|
||||
*
|
||||
* @param endpoint the API endpoint path
|
||||
* @param responseType the expected response type class
|
||||
* @param <T> the response type extending AbstractResponse
|
||||
* @return the parsed response object
|
||||
* @throws CloudflareApiException if an error occurs during the request
|
||||
*/
|
||||
<T extends AbstractResponse> T deleteRequest(String endpoint) throws CloudflareApiException {
|
||||
<T extends AbstractResponse> T deleteRequest(String endpoint, Class<T> responseType) throws CloudflareApiException {
|
||||
HttpDelete request = new HttpDelete(buildUrl(endpoint));
|
||||
return executeRequest(request, (Class<T>) codes.thischwa.cf.model.RecordSingleResponse.class);
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,13 +141,21 @@ abstract class CfBasicHttpClient {
|
||||
|
||||
/**
|
||||
* Sends a PATCH request with a payload to the given endpoint and maps the response.
|
||||
*
|
||||
* @param endpoint the API endpoint path
|
||||
* @param requestPayload the payload to send
|
||||
* @param responseType the expected response type class
|
||||
* @param <T> the response type extending AbstractResponse
|
||||
* @return the parsed response object
|
||||
* @throws CloudflareApiException if an error occurs during the request
|
||||
*/
|
||||
<T extends AbstractResponse> T patchRequest(String endpoint,
|
||||
Object requestPayload)
|
||||
Object requestPayload,
|
||||
Class<T> responseType)
|
||||
throws CloudflareApiException {
|
||||
HttpPatch request = new HttpPatch(buildUrl(endpoint));
|
||||
setRequestPayload(request, requestPayload);
|
||||
return executeRequest(request, (Class<T>) codes.thischwa.cf.model.RecordSingleResponse.class);
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import codes.thischwa.cf.fluent.ZoneOperations;
|
||||
import codes.thischwa.cf.fluent.ZoneOperationsImpl;
|
||||
import codes.thischwa.cf.model.AbstractResponse;
|
||||
import codes.thischwa.cf.model.BatchEntry;
|
||||
import codes.thischwa.cf.model.BatchResponse;
|
||||
@@ -12,7 +14,6 @@ import codes.thischwa.cf.model.ZoneEntity;
|
||||
import codes.thischwa.cf.model.ZoneMultipleResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -29,19 +30,18 @@ import org.jetbrains.annotations.Nullable;
|
||||
* "yourApiKey"
|
||||
* );
|
||||
* // Retrieve a zone
|
||||
* ZoneEntity zone = cfDnsClient.zoneInfo("example.com");
|
||||
* ZoneEntity zone = cfDnsClient.zoneGet("example.com");
|
||||
* System.out.println("Zone ID: " + zone.getId());
|
||||
* // Retrieve records of a subdomain
|
||||
* List<{@link RecordEntity}> records = cfDnsClient.sldListAll(zone, "sld");
|
||||
* List<{@link RecordEntity}> records = cfDnsClient.recordGet(zone, "sld");
|
||||
* records.forEach(record ->
|
||||
* System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
|
||||
* );
|
||||
* // Create a record for the subdomain "api"
|
||||
* RecordEntity created = client.recordCreateSld(zone, "api", 60, RecordType.A, "192.168.10);
|
||||
* RecordEntity created = cfDnsClient.recordCreateSld(zone, "api", 60, RecordType.A, "192.168.1.10");
|
||||
* System.out.println("Created Record ID: " + created.getId());
|
||||
* </code></pre>
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class CfDnsClient extends CfBasicHttpClient {
|
||||
private static final String DEFAULT_BASEURL = "https://api.cloudflare.com/client/v4";
|
||||
@@ -70,15 +70,15 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* process.
|
||||
*/
|
||||
public CfDnsClient(String baseUrl, String authEmail, String authKey) {
|
||||
this(true, baseUrl, authEmail, authKey);
|
||||
this(false, baseUrl, authEmail, authKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@code CfDnsClient}.
|
||||
*
|
||||
* @param emptyResultThrowsException A boolean value indicating whether an exception should be
|
||||
* thrown when the result is empty, it's valid for 'list
|
||||
* requests' only. Default is true.
|
||||
* thrown when the result is empty. Applies to both single and
|
||||
* multiple result requests. Default is false.
|
||||
* @param authEmail The email address associated with the Cloudflare account,
|
||||
* used for authentication.
|
||||
* @param authKey The API key of the Cloudflare account, used as part of the
|
||||
@@ -92,8 +92,8 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* Constructs a new instance of {@code CfDnsClient}.
|
||||
*
|
||||
* @param emptyResultThrowsException A boolean value indicating whether an exception should be
|
||||
* thrown when the result is empty, it's valid for 'list
|
||||
* requests' only. Default is true.
|
||||
* thrown when the result is empty. Applies to both single and
|
||||
* multiple result requests. Default is false.
|
||||
* @param baseUrl The base URL for the Cloudflare API endpoint.
|
||||
* @param authEmail The email associated with the Cloudflare account for
|
||||
* authentication.
|
||||
@@ -110,14 +110,35 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
return sld + "." + zone.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides fluent API access to operations on a specific zone.
|
||||
* This method returns a ZoneOperations interface that allows chaining operations
|
||||
* on DNS records within the specified zone.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre><code>
|
||||
* client.zone("example.com")
|
||||
* .record("api")
|
||||
* .create(RecordType.A, "192.168.1.1", 60);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param zoneName the name of the DNS zone (e.g., "example.com")
|
||||
* @return a ZoneOperations instance for chaining operations
|
||||
* @throws CloudflareApiException if the zone cannot be found or accessed
|
||||
*/
|
||||
public ZoneOperations zone(String zoneName) throws CloudflareApiException {
|
||||
ZoneEntity zoneEntity = zoneGet(zoneName);
|
||||
return new ZoneOperationsImpl(this, zoneEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of all zones from the Cloudflare API.
|
||||
*
|
||||
* @return A list of ZoneEntity objects representing the zones retrieved from the Cloudflare API.
|
||||
* @throws CloudflareApiException If an error occurs during the API request or response handling.
|
||||
*/
|
||||
public List<ZoneEntity> zoneListAll() throws CloudflareApiException {
|
||||
return zoneListAll(PagingRequest.defaultPaging());
|
||||
public List<ZoneEntity> zoneList() throws CloudflareApiException {
|
||||
return zoneList(PagingRequest.defaultPaging());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +150,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* @throws CloudflareApiException if there is an error during the API request or response
|
||||
* processing
|
||||
*/
|
||||
public List<ZoneEntity> zoneListAll(PagingRequest pagingRequest) throws CloudflareApiException {
|
||||
public List<ZoneEntity> zoneList(PagingRequest pagingRequest) throws CloudflareApiException {
|
||||
String endpoint = pagingRequest.addQueryString(CfRequest.ZONE_LIST.buildPath());
|
||||
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
||||
checkResponse(response);
|
||||
@@ -144,7 +165,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* @throws CloudflareApiException If an error occurs while making the API request or processing
|
||||
* the response.
|
||||
*/
|
||||
public ZoneEntity zoneInfo(String name) throws CloudflareApiException {
|
||||
public ZoneEntity zoneGet(String name) throws CloudflareApiException {
|
||||
String endpoint = CfRequest.ZONE_INFO.buildPath(name);
|
||||
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
||||
checkResponse(response, true);
|
||||
@@ -160,8 +181,8 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* @return A list of {@code RecordEntity} associated with the desired SLD.
|
||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
||||
*/
|
||||
public List<RecordEntity> sldListAll(ZoneEntity zone, String sld) throws CloudflareApiException {
|
||||
return sldListAll(zone, sld, PagingRequest.defaultPaging());
|
||||
public List<RecordEntity> recordList(ZoneEntity zone, String sld) throws CloudflareApiException {
|
||||
return recordList(zone, sld, PagingRequest.defaultPaging());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +195,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* @return A list of {@code RecordEntity} associated with the desired SLD.
|
||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
||||
*/
|
||||
public List<RecordEntity> sldListAll(ZoneEntity zone, String sld, PagingRequest pagingRequest)
|
||||
public List<RecordEntity> recordList(ZoneEntity zone, String sld, PagingRequest pagingRequest)
|
||||
throws CloudflareApiException {
|
||||
String fqdn = buildFqdn(zone, sld);
|
||||
String endpoint =
|
||||
@@ -194,12 +215,13 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* @throws CloudflareNotFoundException if the specified SLD is not found in the zone
|
||||
* @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API
|
||||
*/
|
||||
public List<RecordEntity> sldInfo(ZoneEntity zone, String sld) throws CloudflareApiException {
|
||||
return sldInfo(zone, sld, (RecordType[]) null);
|
||||
public List<RecordEntity> recordGet(ZoneEntity zone, String sld) throws CloudflareApiException {
|
||||
return recordGet(zone, sld, (RecordType[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of DNS records for a given second-level domain (SLD) within a specific zone.
|
||||
* Optionally filters by one or more DNS record types.
|
||||
*
|
||||
* @param zone The zone entity containing information about the domain zone.
|
||||
* @param sld The second-level domain (SLD) for which to retrieve DNS records.
|
||||
@@ -208,7 +230,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
* @throws CloudflareNotFoundException if the specified SLD is not found in the zone
|
||||
* @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API
|
||||
*/
|
||||
public List<RecordEntity> sldInfo(ZoneEntity zone, String sld, @Nullable RecordType... types)
|
||||
public List<RecordEntity> recordGet(ZoneEntity zone, String sld, @Nullable RecordType... types)
|
||||
throws CloudflareApiException {
|
||||
String fqdn = buildFqdn(zone, sld);
|
||||
String endpoint = buildEndpointWithTypeFilters(zone.getId(), fqdn, types);
|
||||
@@ -222,12 +244,11 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
if (types == null || types.length == 0) {
|
||||
return baseEndpoint;
|
||||
}
|
||||
StringBuilder queryParams = new StringBuilder();
|
||||
StringBuilder endpoint = new StringBuilder(baseEndpoint);
|
||||
for (RecordType type : types) {
|
||||
queryParams.append("&");
|
||||
queryParams.append("type=").append(type);
|
||||
endpoint.append("&type=").append(type);
|
||||
}
|
||||
return baseEndpoint + queryParams;
|
||||
return endpoint.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,7 +302,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
String endpoint = CfRequest.RECORD_CREATE.buildPath(zone.getId());
|
||||
RecordSingleResponse resp = postRequest(endpoint, rec, RecordSingleResponse.class);
|
||||
checkResponse(resp);
|
||||
log.info("Record {} of type {} successful created.", rec.getName(), rec.getType());
|
||||
log.info("Record {} of type {} successful created.", rec.getSld(), rec.getType());
|
||||
return resp.getResult();
|
||||
}
|
||||
|
||||
@@ -297,9 +318,9 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
public boolean recordDelete(ZoneEntity zone, RecordEntity rec) throws CloudflareApiException {
|
||||
boolean changed = recordDelete(zone, rec.getId());
|
||||
if (changed) {
|
||||
log.debug("Record {} of the type [{}] successful deleted.", rec.getName(), rec.getType());
|
||||
log.debug("Record {} of the type [{}] successful deleted.", rec.getSld(), rec.getType());
|
||||
} else {
|
||||
log.warn("Record {} of the type [{}] was not deleted.", rec.getName(), rec.getType());
|
||||
log.warn("Record {} of the type [{}] was not deleted.", rec.getSld(), rec.getType());
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
@@ -315,7 +336,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
*/
|
||||
public boolean recordDelete(ZoneEntity zone, String id) throws CloudflareApiException {
|
||||
String endpoint = CfRequest.RECORD_DELETE.buildPath(zone.getId(), id);
|
||||
RecordSingleResponse resp = deleteRequest(endpoint);
|
||||
RecordSingleResponse resp = deleteRequest(endpoint, RecordSingleResponse.class);
|
||||
checkResponse(resp);
|
||||
log.debug("Record id#{} successful deleted.", id);
|
||||
return resp.getResult().getId().equals(id);
|
||||
@@ -336,9 +357,9 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
rec.setModifiedOn(null);
|
||||
rec.setCreatedOn(null);
|
||||
String endpoint = CfRequest.RECORD_UPDATE.buildPath(zone.getId(), rec.getId());
|
||||
RecordSingleResponse resp = patchRequest(endpoint, rec);
|
||||
RecordSingleResponse resp = patchRequest(endpoint, rec, RecordSingleResponse.class);
|
||||
checkResponse(resp);
|
||||
log.info("Record {} of type {} successful updated.", rec.getName(), rec.getType());
|
||||
log.info("Record {} of type {} successful updated.", rec.getSld(), rec.getType());
|
||||
return resp.getResult();
|
||||
}
|
||||
|
||||
@@ -356,7 +377,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
String fqdn = buildFqdn(zone, sld);
|
||||
List<RecordEntity> recs;
|
||||
try {
|
||||
recs = sldInfo(zone, sld, recordTypes);
|
||||
recs = recordGet(zone, sld, recordTypes);
|
||||
} catch (CloudflareNotFoundException e) {
|
||||
log.trace("No record of type {} found for domain {}.", recordTypes, fqdn);
|
||||
return;
|
||||
@@ -390,27 +411,16 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
BatchEntry batchEntry = new BatchEntry();
|
||||
// build 'clean' record entries
|
||||
if (postRecords != null) {
|
||||
List<RecordEntity> cleanedPosts = new ArrayList<>();
|
||||
postRecords.forEach(
|
||||
rec -> cleanedPosts.add(RecordEntity.build(rec.getId(), rec.getName(), rec.getType(), rec.getTtl(), rec.getContent())));
|
||||
batchEntry.setPosts(cleanedPosts);
|
||||
batchEntry.setPosts(cleanRecordsForPostOrPut(postRecords));
|
||||
}
|
||||
if (putRecords != null) {
|
||||
List<RecordEntity> cleanedPuts = new ArrayList<>();
|
||||
putRecords.forEach(
|
||||
rec -> cleanedPuts.add(RecordEntity.build(rec.getId(), rec.getName(), rec.getType(), rec.getTtl(), rec.getContent())));
|
||||
batchEntry.setPuts(cleanedPuts);
|
||||
batchEntry.setPuts(cleanRecordsForPostOrPut(putRecords));
|
||||
}
|
||||
if (patchRecords != null) {
|
||||
List<RecordEntity> cleanedPatches = new ArrayList<>();
|
||||
patchRecords.forEach(rec -> cleanedPatches.add(RecordEntity.build(rec.getId(), rec.getContent())));
|
||||
batchEntry.setPatches(cleanedPatches);
|
||||
batchEntry.setPatches(cleanRecordsForPatch(patchRecords));
|
||||
}
|
||||
if (deleteRecords != null) {
|
||||
List<RecordEntity> cleanedDeletes = new ArrayList<>();
|
||||
deleteRecords.forEach(
|
||||
rec -> cleanedDeletes.add(RecordEntity.build(rec.getId(), rec.getName(), rec.getType(), null, rec.getContent())));
|
||||
batchEntry.setDeletes(cleanedDeletes);
|
||||
batchEntry.setDeletes(cleanRecordsForDelete(deleteRecords));
|
||||
}
|
||||
|
||||
String endpoint = CfRequest.RECORD_BATCH.buildPath(zone.getId());
|
||||
@@ -419,16 +429,40 @@ public class CfDnsClient extends CfBasicHttpClient {
|
||||
|
||||
// set zone id
|
||||
BatchEntry result = resp.getResult();
|
||||
setZoneIdForBatchResults(result, zone.getId());
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<RecordEntity> cleanRecordsForPostOrPut(List<RecordEntity> records) {
|
||||
List<RecordEntity> cleaned = new ArrayList<>();
|
||||
records.forEach(
|
||||
rec -> cleaned.add(RecordEntity.build(rec.getId(), rec.getSld(), rec.getType(), rec.getTtl(), rec.getContent())));
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private List<RecordEntity> cleanRecordsForPatch(List<RecordEntity> records) {
|
||||
List<RecordEntity> cleaned = new ArrayList<>();
|
||||
records.forEach(rec -> cleaned.add(RecordEntity.build(rec.getId(), rec.getContent())));
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private List<RecordEntity> cleanRecordsForDelete(List<RecordEntity> records) {
|
||||
List<RecordEntity> cleaned = new ArrayList<>();
|
||||
records.forEach(
|
||||
rec -> cleaned.add(RecordEntity.build(rec.getId(), rec.getSld(), rec.getType(), null, rec.getContent())));
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private void setZoneIdForBatchResults(BatchEntry result, String zoneId) {
|
||||
if (result.getPosts() != null) {
|
||||
result.getPosts().forEach(rec -> rec.setZoneId(zone.getId()));
|
||||
result.getPosts().forEach(rec -> rec.setZoneId(zoneId));
|
||||
}
|
||||
if (result.getPuts() != null) {
|
||||
result.getPuts().forEach(rec -> rec.setZoneId(zone.getId()));
|
||||
result.getPuts().forEach(rec -> rec.setZoneId(zoneId));
|
||||
}
|
||||
if (result.getPatches() != null) {
|
||||
result.getPatches().forEach(rec -> rec.setZoneId(zone.getId()));
|
||||
result.getPatches().forEach(rec -> rec.setZoneId(zoneId));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkResponse(AbstractResponse resp) throws CloudflareApiException {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import codes.thischwa.cf.model.AbstractResponse;
|
||||
import codes.thischwa.cf.model.AbstractSingleResponse;
|
||||
import codes.thischwa.cf.model.RecordMultipleResponse;
|
||||
import codes.thischwa.cf.model.ResponseResultInfo;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -14,9 +15,11 @@ import java.util.stream.Collectors;
|
||||
* <li>It checks whether the API response was successful by analyzing the associated response
|
||||
* metadata. If the response indicates failure, an exception is thrown with descriptive error
|
||||
* messages.
|
||||
* <li>If a {@link RecordMultipleResponse} is used, it validates the number of results in the API
|
||||
* response payload to detect unexpected counts. Depending on the parameter
|
||||
* 'emptyResultThrowsException', an exception will be triggered or an empty result will be returned.
|
||||
* <li>It validates the number of results in the API response payload to detect unexpected counts.
|
||||
* For {@link RecordMultipleResponse}, it checks if results are empty or if more than one result
|
||||
* was returned when a single result was expected. For {@link AbstractSingleResponse}, it checks
|
||||
* if the result is null. Depending on the parameter 'emptyResultThrowsException', an exception
|
||||
* will be triggered or an empty/null result will be returned.
|
||||
* </ul>
|
||||
*/
|
||||
class ResponseValidator {
|
||||
@@ -50,6 +53,10 @@ class ResponseValidator {
|
||||
if (emptyResultThrowsException && respMulti.getResultInfo().totalCount() == 0) {
|
||||
throw new CloudflareNotFoundException("No result found");
|
||||
}
|
||||
} else if (resp instanceof AbstractSingleResponse<?> respSingle) {
|
||||
if (emptyResultThrowsException && respSingle.getResult() == null) {
|
||||
throw new CloudflareNotFoundException("No result found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package codes.thischwa.cf.fluent;
|
||||
|
||||
import codes.thischwa.cf.CloudflareApiException;
|
||||
import codes.thischwa.cf.model.RecordEntity;
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Fluent interface for record-level operations.
|
||||
* Provides a chainable API for CRUD operations on DNS records.
|
||||
*/
|
||||
public interface RecordOperations {
|
||||
|
||||
/**
|
||||
* Retrieves DNS records for the selected subdomain.
|
||||
*
|
||||
* @return a list of RecordEntity objects matching the criteria
|
||||
* @throws CloudflareApiException if an error occurs while retrieving records
|
||||
*/
|
||||
List<RecordEntity> get() throws CloudflareApiException;
|
||||
|
||||
/**
|
||||
* Creates a new DNS record with the specified parameters.
|
||||
*
|
||||
* @param type the DNS record type (e.g., A, AAAA, CNAME)
|
||||
* @param content the content of the DNS record (e.g., IP address)
|
||||
* @param ttl the time-to-live value in seconds
|
||||
* @return the created RecordEntity
|
||||
* @throws CloudflareApiException if an error occurs while creating the record
|
||||
*/
|
||||
RecordEntity create(RecordType type, String content, int ttl) throws CloudflareApiException;
|
||||
|
||||
/**
|
||||
* Updates an existing DNS record with new content.
|
||||
*
|
||||
* @param newContent the new content for the DNS record
|
||||
* @return the updated RecordEntity
|
||||
* @throws CloudflareApiException if an error occurs while updating the record
|
||||
*/
|
||||
RecordEntity update(String newContent) throws CloudflareApiException;
|
||||
|
||||
/**
|
||||
* Deletes DNS records of the specified types.
|
||||
*
|
||||
* @param types the DNS record types to delete
|
||||
* @throws CloudflareApiException if an error occurs while deleting records
|
||||
*/
|
||||
void delete(RecordType... types) throws CloudflareApiException;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package codes.thischwa.cf.fluent;
|
||||
|
||||
import codes.thischwa.cf.CfDnsClient;
|
||||
import codes.thischwa.cf.CloudflareApiException;
|
||||
import codes.thischwa.cf.model.RecordEntity;
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import codes.thischwa.cf.model.ZoneEntity;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of RecordOperations for fluent API access to record-level operations.
|
||||
*/
|
||||
public class RecordOperationsImpl implements RecordOperations {
|
||||
|
||||
private final CfDnsClient client;
|
||||
private final ZoneEntity zone;
|
||||
private final String sld;
|
||||
private final RecordType[] types;
|
||||
|
||||
/**
|
||||
* Constructs a RecordOperationsImpl instance.
|
||||
*
|
||||
* @param client the CfDnsClient instance to use for operations
|
||||
* @param zone the ZoneEntity representing the DNS zone
|
||||
* @param sld the subdomain (second-level domain) name
|
||||
* @param types optional array of RecordType to filter by
|
||||
*/
|
||||
public RecordOperationsImpl(CfDnsClient client, ZoneEntity zone, String sld, @Nullable RecordType[] types) {
|
||||
this.client = client;
|
||||
this.zone = zone;
|
||||
this.sld = sld;
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RecordEntity> get() throws CloudflareApiException {
|
||||
if (types == null || types.length == 0) {
|
||||
return client.recordGet(zone, sld);
|
||||
}
|
||||
return client.recordGet(zone, sld, types);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordEntity create(RecordType type, String content, int ttl) throws CloudflareApiException {
|
||||
return client.recordCreateSld(zone, sld, ttl, type, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordEntity update(String newContent) throws CloudflareApiException {
|
||||
List<RecordEntity> records = get();
|
||||
if (records.isEmpty()) {
|
||||
throw new CloudflareApiException("No records found to update for subdomain: " + sld);
|
||||
}
|
||||
if (records.size() > 1) {
|
||||
throw new CloudflareApiException("Multiple records found. Please use recordUpdate() directly for precise control.");
|
||||
}
|
||||
RecordEntity record = records.get(0);
|
||||
record.setContent(newContent);
|
||||
return client.recordUpdate(zone, record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(RecordType... types) throws CloudflareApiException {
|
||||
client.recordDeleteTypeIfExists(zone, sld, types);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package codes.thischwa.cf.fluent;
|
||||
|
||||
import codes.thischwa.cf.CloudflareApiException;
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Fluent interface for zone-level operations.
|
||||
* Provides a chainable API for accessing and manipulating DNS records within a specific zone.
|
||||
*/
|
||||
public interface ZoneOperations {
|
||||
|
||||
/**
|
||||
* Selects a record (subdomain) within the zone for further operations.
|
||||
*
|
||||
* @param sld the second-level domain (subdomain) name
|
||||
* @return a RecordOperations instance for chaining record-specific operations
|
||||
* @throws CloudflareApiException if the zone cannot be found or accessed
|
||||
*/
|
||||
RecordOperations record(String sld) throws CloudflareApiException;
|
||||
|
||||
/**
|
||||
* Selects a record with specific types within the zone for further operations.
|
||||
*
|
||||
* @param sld the second-level domain (subdomain) name
|
||||
* @param types optional DNS record types to filter by
|
||||
* @return a RecordOperations instance for chaining record-specific operations
|
||||
* @throws CloudflareApiException if the zone cannot be found or accessed
|
||||
*/
|
||||
RecordOperations record(String sld, @Nullable RecordType... types) throws CloudflareApiException;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package codes.thischwa.cf.fluent;
|
||||
|
||||
import codes.thischwa.cf.CfDnsClient;
|
||||
import codes.thischwa.cf.CloudflareApiException;
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import codes.thischwa.cf.model.ZoneEntity;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of ZoneOperations for fluent API access to zone-level operations.
|
||||
*/
|
||||
public class ZoneOperationsImpl implements ZoneOperations {
|
||||
|
||||
private final CfDnsClient client;
|
||||
private final ZoneEntity zone;
|
||||
|
||||
/**
|
||||
* Constructs a ZoneOperationsImpl instance.
|
||||
*
|
||||
* @param client the CfDnsClient instance to use for operations
|
||||
* @param zone the ZoneEntity representing the DNS zone
|
||||
*/
|
||||
public ZoneOperationsImpl(CfDnsClient client, ZoneEntity zone) {
|
||||
this.client = client;
|
||||
this.zone = zone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordOperations record(String sld) throws CloudflareApiException {
|
||||
return new RecordOperationsImpl(client, zone, sld, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordOperations record(String sld, @Nullable RecordType... types) throws CloudflareApiException {
|
||||
return new RecordOperationsImpl(client, zone, sld, types);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Fluent API interfaces and implementations for chainable DNS operations.
|
||||
*
|
||||
* <p>This package provides a fluent, chainable interface for interacting with Cloudflare DNS
|
||||
* records, making code more readable and concise.
|
||||
*
|
||||
* <p>Example usage:
|
||||
* <pre><code>
|
||||
* // Create a DNS record
|
||||
* client.zone("example.com")
|
||||
* .record("api")
|
||||
* .create(RecordType.A, "192.168.1.1", 60);
|
||||
*
|
||||
* // Get DNS records
|
||||
* List<RecordEntity> records = client.zone("example.com")
|
||||
* .record("www", RecordType.A)
|
||||
* .get();
|
||||
*
|
||||
* // Update a DNS record
|
||||
* client.zone("example.com")
|
||||
* .record("api", RecordType.A)
|
||||
* .update("192.168.1.2");
|
||||
*
|
||||
* // Delete DNS records
|
||||
* client.zone("example.com")
|
||||
* .record("old-service")
|
||||
* .delete(RecordType.A, RecordType.AAAA);
|
||||
* </code></pre>
|
||||
*/
|
||||
|
||||
package codes.thischwa.cf.fluent;
|
||||
@@ -21,6 +21,12 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public class PagingRequest {
|
||||
/**
|
||||
* Default page size for retrieving all records in a single request.
|
||||
* Set to a very high value to effectively disable pagination when fetching all records.
|
||||
*/
|
||||
private static final int DEFAULT_ALL_RECORDS_PAGE_SIZE = 5_000_000;
|
||||
|
||||
private int page;
|
||||
private int perPage;
|
||||
|
||||
@@ -42,19 +48,19 @@ public class PagingRequest {
|
||||
|
||||
/**
|
||||
* Creates a default {@code PagingRequest} instance with a page number set to 1 and a high number
|
||||
* of items per page (5,000,000) to accommodate large dataset requests.
|
||||
* of items per page to accommodate large dataset requests and effectively retrieve all records.
|
||||
*
|
||||
* @return a default {@code PagingRequest} instance with predefined pagination parameters
|
||||
*/
|
||||
public static PagingRequest defaultPaging() {
|
||||
return new PagingRequest(1, 5000000);
|
||||
return new PagingRequest(1, DEFAULT_ALL_RECORDS_PAGE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the pagination parameters in a key-value map format.
|
||||
*
|
||||
* @return a map containing the pagination parameters, where the key "page" indicates the current
|
||||
* page number and the key "perPage" indicates the number of items per page.
|
||||
* page number and the key "perPage" indicates the number of items per page.
|
||||
*/
|
||||
public Map<String, String> getPagingParams() {
|
||||
return Map.of("page", String.valueOf(page), "perPage", String.valueOf(perPage));
|
||||
|
||||
@@ -94,27 +94,45 @@ public class RecordEntity extends AbstractEntity {
|
||||
* @param ttl the time-to-live (TTL) value for the DNS record
|
||||
* @param content the content of the DNS record, typically an IP address or other record data
|
||||
* @return a {@link RecordEntity} populated with the provided attributes
|
||||
* @throws IllegalArgumentException if the type string is not a valid RecordType
|
||||
*/
|
||||
public static RecordEntity build(String id, String name, String type, Integer ttl, String content) {
|
||||
RecordEntity rec = build(name, RecordType.valueOf(type), ttl, content);
|
||||
RecordType recordType;
|
||||
try {
|
||||
recordType = RecordType.valueOf(type);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Invalid record type: " + type + ". Must be one of: "
|
||||
+ java.util.Arrays.toString(RecordType.values()), e);
|
||||
}
|
||||
RecordEntity rec = build(name, recordType, ttl, content);
|
||||
rec.setId(id);
|
||||
return rec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the DNS record.
|
||||
* Retrieves the short name (subdomain) of the DNS record.
|
||||
* If the name contains a dot ('.'), only the substring before the first dot is returned.
|
||||
* This is useful for getting the subdomain part of a fully qualified domain name.
|
||||
*
|
||||
* @return the name of the DNS record, potentially truncated before the first dot,
|
||||
* or the full name if no dot is present.
|
||||
* @return the short name of the DNS record (substring before the first dot),
|
||||
* or the full name if no dot is present
|
||||
*/
|
||||
public String getName() {
|
||||
if (name != null) {
|
||||
int pos = name.indexOf('.');
|
||||
if (pos > 0) {
|
||||
return name.substring(0, pos);
|
||||
}
|
||||
public String getSld() {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (zoneName != null && name.endsWith(zoneName)) {
|
||||
int zoneNameLength = zoneName.length();
|
||||
int dotSeparatorLength = 1;
|
||||
return name.substring(0, name.length() - zoneNameLength - dotSeparatorLength);
|
||||
}
|
||||
|
||||
int firstDotPosition = name.indexOf('.');
|
||||
if (firstDotPosition > 0) {
|
||||
return name.substring(0, firstDotPosition);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user