diff --git a/src/main/java/codes/thischwa/cf/CfDnsClient.java b/src/main/java/codes/thischwa/cf/CfDnsClient.java index d482fed..c51605d 100644 --- a/src/main/java/codes/thischwa/cf/CfDnsClient.java +++ b/src/main/java/codes/thischwa/cf/CfDnsClient.java @@ -116,7 +116,7 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Provides fluent API access to operations on a specific zone. + * Provides fluent API access to operations in a specific zone. * This method returns a ZoneOperations interface that allows chaining operations * on DNS records within the specified zone. * @@ -177,6 +177,33 @@ public class CfDnsClient extends CfBasicHttpClient { return response.getResult().get(0); } + /** + * Retrieves a list of DNS records for a specified zone, with optional paging support. + * + * @param zone The zone entity containing information about the target zone. + * @return A list of RecordEntity objects representing the DNS records of the specified zone. + * @throws CloudflareApiException If an error occurs during the API request or response processing. + */ + public List recordList(ZoneEntity zone) throws CloudflareApiException { + return recordList(zone, (PagingRequest) null); + } + + /** + * Retrieves a list of DNS records for a specified zone, with optional paging support. + * + * @param zone The zone entity containing information about the target zone. + * @param pagingRequest The paging request containing parameters such as page size and number. + * @return A list of RecordEntity objects representing the DNS records of the specified zone. + * @throws CloudflareApiException If an error occurs during the API request or response processing. + */ + public List recordList(ZoneEntity zone, @Nullable PagingRequest pagingRequest) throws CloudflareApiException { + PagingRequest pr = pagingRequest == null ? PagingRequest.defaultPaging() : pagingRequest; + String endpoint = pr.addQueryString(CfRequest.RECORD_LIST.buildPath(zone.getId())); + RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class); + checkResponse(resp); + return resp.getResult(); + } + /** * Retrieves DNS records for the specified second-level domain (SLD) within a zone. * @@ -203,35 +230,17 @@ public class CfDnsClient extends CfBasicHttpClient { */ public List recordList(ZoneEntity zone, String sld, @Nullable RecordType... types) throws CloudflareApiException { - PagingRequest pagingRequest = PagingRequest.defaultPaging(); - List recs = recordList(zone, sld, pagingRequest); - return filterAndSetZoneRecords(zone, types, recs); - } - - - /** - * Retrieves all getRecord entities for a specific second-level domain (SLD) within a given DNS - * zone using the provided paging request parameters. - * - * @param zone The DNS zone entity for which the SLD records are to be fetched. - * @param sld The second-level domain name for which the records are retrieved. - * @param pagingRequest The paging request. - * @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 recordList(ZoneEntity zone, String sld, PagingRequest pagingRequest) - throws CloudflareApiException { String fqdn = buildFqdn(zone, sld); - String endpoint = - pagingRequest.addQueryString(CfRequest.RECORD_INFO_NAME.buildPath(zone.getId(), fqdn)); + String endpoint = CfRequest.RECORD_LIST_NAME.buildPath(zone.getId(), fqdn); RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class); - checkResponse(resp); - return resp.getResult(); + checkResponse(resp, false); + List recs = resp.getResult(); + return filterAndSetZoneRecords(zone, types, recs); } /** * Retrieves a list of all DNS records for a given zone. - * Optionally filters by one or more DNS getRecord types. + * Optionally, filters by one or more DNS getRecord types. * * @param zone The zone entity containing information about the domain zone. * @param types Optional parameter specifying one or more DNS getRecord types to filter the results. @@ -438,7 +447,7 @@ public class CfDnsClient extends CfBasicHttpClient { Set allowedTypes = new HashSet<>(Arrays.asList(types)); filtered = recs.stream() .filter(rec -> allowedTypes.contains(RecordType.valueOf(rec.getType()))) - .collect(Collectors.toList());; + .collect(Collectors.toList()); } else { filtered = new ArrayList<>(recs); } diff --git a/src/main/java/codes/thischwa/cf/CfRequest.java b/src/main/java/codes/thischwa/cf/CfRequest.java index cd7f3e9..c2573de 100644 --- a/src/main/java/codes/thischwa/cf/CfRequest.java +++ b/src/main/java/codes/thischwa/cf/CfRequest.java @@ -26,25 +26,24 @@ public enum CfRequest { * needs to be provided to construct the complete path. */ RECORD_LIST("/zones/%s/dns_records"), + /** + * Represents the API endpoint path for retrieving information about a DNS getRecord within a + * specific DNS zone by its name. The endpoint path includes placeholders for the zone identifier + * and the getRecord name, which need to be provided to construct the complete path. + */ + RECORD_LIST_NAME("/zones/%s/dns_records?name=%s"), /** * Represents the API endpoint path for creating a new DNS getRecord within a specific DNS zone. The * endpoint path includes a placeholder for the zone identifier, which needs to be provided to * construct the complete path. */ RECORD_CREATE("/zones/%s/dns_records"), - /** - * Represents the API endpoint path for retrieving information about a DNS getRecord within a - * specific DNS zone by its name. The endpoint path includes placeholders for the zone identifier - * and the getRecord name, which need to be provided to construct the complete path. - */ - RECORD_INFO_NAME("/zones/%s/dns_records?name=%s"), /** * Represents the API endpoint path for updating an existing DNS getRecord within a specific DNS * zone. The endpoint path includes placeholders for the zone identifier and the getRecord * identifier, which need to be provided to construct the complete path. */ RECORD_UPDATE("/zones/%s/dns_records/%s"), - /** * Represents the API endpoint path for performing batch operations on DNS records within a specific zone. * The placeholder "%s" in the path is intended to be replaced by a zone identifier. diff --git a/src/main/java/codes/thischwa/cf/model/PagingRequest.java b/src/main/java/codes/thischwa/cf/model/PagingRequest.java index ac061a3..773d002 100644 --- a/src/main/java/codes/thischwa/cf/model/PagingRequest.java +++ b/src/main/java/codes/thischwa/cf/model/PagingRequest.java @@ -25,7 +25,7 @@ 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 static final int DEFAULT_ALL_RECORDS_PAGE_SIZE = 1000; private int page; private int perPage; @@ -77,7 +77,7 @@ public class PagingRequest { } private String queryString(boolean add) { - String qs = "page=" + page + "&perPage=" + perPage; + String qs = "page=" + page + "&per_page=" + perPage; return add ? "&" + qs : "?" + qs; } } diff --git a/src/test/java/codes/thischwa/cf/CfClientTest.java b/src/test/java/codes/thischwa/cf/CfClientTest.java index 0201359..75e06dd 100644 --- a/src/test/java/codes/thischwa/cf/CfClientTest.java +++ b/src/test/java/codes/thischwa/cf/CfClientTest.java @@ -9,14 +9,18 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import codes.thischwa.cf.model.BatchEntry; +import codes.thischwa.cf.model.PagingRequest; import codes.thischwa.cf.model.RecordEntity; import codes.thischwa.cf.model.RecordType; import codes.thischwa.cf.model.ZoneEntity; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -91,6 +95,19 @@ public class CfClientTest { } } + @Test + void testZoneList() throws CloudflareApiException { + List zones = client.zoneList(); + assertNotNull(zones); + assertFalse(zones.isEmpty()); + assertEquals(ZONE_STR, zones.get(0).getName()); + + zones = client.zoneList(PagingRequest.of(1, 100)); + assertNotNull(zones); + assertFalse(zones.isEmpty()); + assertEquals(ZONE_STR, zones.get(0).getName()); + } + @Test void testZoneListAnlFailedSldList() throws Exception { List zList = client.zoneList(); @@ -445,4 +462,68 @@ public class CfClientTest { } + @Test + void testPaging() throws Exception { + ZoneEntity zone = client.zoneGet(ZONE_STR); + String pagingSld = "paging-" + System.currentTimeMillis(); + + try { + int existingCount = 0; + try { + List allRecords = client.recordList(zone); + existingCount = allRecords.size(); + } catch (CloudflareApiException e) { + // ignore + } + + // Calculate how many records we need to create to reach at least 12 total A records + // (to test paging with pageSize 5: page 1 = 5, page 2 = 5, page 3 = 2+) + int targetCount = 12; + int recordsToCreate = Math.max(0, targetCount - existingCount); + + // Create additional A records if needed + List createdRecords = new ArrayList<>(); + for (int i = 1; i <= recordsToCreate; i++) { + RecordEntity record = RecordEntity.build(pagingSld, RecordType.A, TTL, "127.0.0." + i); + RecordEntity created = client.recordCreate(zone, record); + createdRecords.add(created); + assertNotNull(created.getId()); + } + + // Test paging with page size of 5 + PagingRequest page1Request = PagingRequest.of(1, 5); + List page1Records = client.recordList(zone, page1Request); + assertEquals(5, page1Records.size(), "First page should contain 5 records"); + + // 2nd page should also contain 5 records (if we have at least 12 total) + PagingRequest page2Request = PagingRequest.of(2, 5); + List page2Records = client.recordList(zone, page2Request); + assertEquals(5, page2Records.size(), "Second page should contain at least 5 records"); + + // 3rd page should contain 2 records + PagingRequest page3Request = PagingRequest.of(3, 5); + List page3Records = client.recordList(zone, page3Request); + assertEquals(2, page3Records.size(), "Third page should contain 2 records"); + + // Verify no overlap between pages + List page1Ids = page1Records.stream().map(RecordEntity::getId).toList(); + List page2Ids = page2Records.stream().map(RecordEntity::getId).toList(); + List page3Ids = page3Records.stream().map(RecordEntity::getId).toList(); + Set generatedRecordIds = new HashSet<>(page1Ids); + generatedRecordIds.addAll(page2Ids); + generatedRecordIds.addAll(page3Ids); + assertEquals(createdRecords.size(), generatedRecordIds.size()); + + // Verify our created records are in the zone + List allRecords = client.recordList(zone); + Set allRecordIds = allRecords.stream().map(RecordEntity::getId).collect(Collectors.toSet()); + assertEquals(createdRecords.size(), allRecordIds.size()); + assertTrue(allRecordIds.containsAll(generatedRecordIds)); + } finally { + try { + client.recordDeleteTypeIfExists(zone, pagingSld, RecordType.A); + } catch (Exception e) { /* ignore */ } + } + } + } diff --git a/src/test/java/codes/thischwa/cf/CfRequestTest.java b/src/test/java/codes/thischwa/cf/CfRequestTest.java index 1990c2c..14aa061 100644 --- a/src/test/java/codes/thischwa/cf/CfRequestTest.java +++ b/src/test/java/codes/thischwa/cf/CfRequestTest.java @@ -27,7 +27,7 @@ public class CfRequestTest { @Test public void testBuildRecordInfoName() { - String result = CfRequest.RECORD_INFO_NAME.buildPath("zone123", "sub.domain.com"); + String result = CfRequest.RECORD_LIST_NAME.buildPath("zone123", "sub.domain.com"); assertEquals("/zones/zone123/dns_records?name=sub.domain.com", result); } @@ -45,7 +45,7 @@ public class CfRequestTest { @Test public void testBuildRecordInfo() { - String result = CfRequest.RECORD_INFO_NAME.buildPath("zone123", "sld.domain.com"); + String result = CfRequest.RECORD_LIST_NAME.buildPath("zone123", "sld.domain.com"); assertEquals("/zones/zone123/dns_records?name=sld.domain.com", result); } diff --git a/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java b/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java index 0e88c79..f399942 100644 --- a/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java +++ b/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java @@ -9,13 +9,13 @@ public class PagingRequestTest { @Test void testBuildPath() { String result = PagingRequest.defaultPaging().addQueryString("/zones"); - assertEquals("/zones?page=1&perPage=5000000", result); + assertEquals("/zones?page=1&per_page=1000", result); } @Test void testBuildPathAdditional() { String result = new PagingRequest( 10, 100).addQueryString("/zones?foo=bar"); - assertEquals("/zones?foo=bar&page=10&perPage=100", result); + assertEquals("/zones?foo=bar&page=10&per_page=100", result); } @Test diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 42dc8c6..f272639 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -1,5 +1,5 @@ - +