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:
@@ -50,6 +50,16 @@ The dependency is:
|
||||
## Changelog
|
||||
|
||||
- 0.2.0-beta-SNAPSHOT:
|
||||
- **New Fluent API**: Added chainable method interface for more readable DNS operations (
|
||||
`client.zone().record()...`)
|
||||
- **Breaking Change**: `emptyResultThrowsException` default changed from `true` to `false`. Now applies to both
|
||||
single and multiple result requests. Empty results will be returned by default without throwing exceptions.
|
||||
- API method names refactored for consistency: `zoneListAll` → `zoneList`, `zoneInfo` → `zoneGet`, `sldListAll` →
|
||||
`recordList`, `sldInfo` → `recordGet`
|
||||
- RecordEntity getter methods renamed for clarity: `getName()` → `getSld()`
|
||||
- Code quality improvements: eliminated duplication in batch operations, improved type safety in HTTP methods,
|
||||
optimized string concatenation, removed mutable setters from CfDnsClient
|
||||
- Enhanced type validation in `RecordEntity.build()` with better error messages
|
||||
- CfClient#sldInfo must return multiple RecordEntries
|
||||
- add a missing source jar
|
||||
- ResponseResultInfo#Errors: wrong object structure
|
||||
@@ -68,6 +78,11 @@ The methods can be categorized as follows:
|
||||
- `Zone`: list, info
|
||||
- `Record`: list, info, create, update, delete
|
||||
|
||||
The API provides two styles for working with DNS records:
|
||||
|
||||
1. **Traditional API**: Direct method calls with explicit parameters
|
||||
2. **Fluent API**: Chainable method calls for more readable code
|
||||
|
||||
The following text focuses on the basic methods. For further information, take a look at
|
||||
the [javadoc of the CfDnsClient](https://cloudflaredns-java-f4ee3a.gitlab.io/apidocs/codes/thischwa/cf/CfDnsClient.html).
|
||||
|
||||
@@ -79,20 +94,20 @@ CfDnsClient cfDnsClient = new CfDnsClient(
|
||||
);
|
||||
```
|
||||
|
||||
### `zoneListAll`
|
||||
### `zoneList`
|
||||
|
||||
Retrieve all zones within the Cloudflare account.
|
||||
|
||||
- **Returns**: A list of `ZoneEntity` objects.
|
||||
|
||||
```java
|
||||
List<ZoneEntity> zones = cfDnsClient.zoneListAll();
|
||||
List<ZoneEntity> zones = cfDnsClient.zoneList();
|
||||
zones.forEach(zone -> System.out.println("Zone: " + zone.getName()));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `zoneInfo`
|
||||
### `zoneGet`
|
||||
|
||||
Get detailed information about a specific zone by its name.
|
||||
|
||||
@@ -101,13 +116,13 @@ Get detailed information about a specific zone by its name.
|
||||
- **Returns**: A `ZoneEntity` object.
|
||||
|
||||
```java
|
||||
ZoneEntity zone = cfDnsClient.zoneInfo("example.com");
|
||||
ZoneEntity zone = cfDnsClient.zoneGet("example.com");
|
||||
System.out.println("Zone ID: " + zone.getId());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `sldListAll`
|
||||
### `recordList`
|
||||
|
||||
Retrieve all records for a specific second-level domain (SLD) under a given zone.
|
||||
|
||||
@@ -117,15 +132,17 @@ Retrieve all records for a specific second-level domain (SLD) under a given zone
|
||||
- **Returns**: A list of `RecordEntity` objects.
|
||||
|
||||
```java
|
||||
List<RecordEntity> records = cfDnsClient.sldListAll(zone, "sld");
|
||||
records.forEach(record ->
|
||||
List<RecordEntity> records = cfDnsClient.recordList(zone, "sld");
|
||||
records.
|
||||
|
||||
forEach(record ->
|
||||
System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `sldInfo`
|
||||
### `recordGet`
|
||||
|
||||
Retrieve DNS record details for a specific SLD and zone, optionally filtered by record types.
|
||||
|
||||
@@ -138,28 +155,28 @@ Retrieve DNS record details for a specific SLD and zone, optionally filtered by
|
||||
|
||||
```java
|
||||
// Get all records for a specific SLD
|
||||
List<RecordEntity> allRecords = cfDnsClient.sldInfo(zone, "www");
|
||||
List<RecordEntity> allRecords = cfDnsClient.recordGet(zone, "www");
|
||||
allRecords.
|
||||
|
||||
forEach(record ->
|
||||
System.out.
|
||||
|
||||
println("Type: "+record.getType() +", Content: "+record.
|
||||
println("Type: "+record.getType()+", Content: "+record.
|
||||
|
||||
getContent())
|
||||
);
|
||||
getContent()));
|
||||
|
||||
// Get only A records
|
||||
List<RecordEntity> aRecords = cfDnsClient.sldInfo(zone, "www", RecordType.A);
|
||||
List<RecordEntity> aRecords = cfDnsClient.recordGet(zone, "www", RecordType.A);
|
||||
System.out.
|
||||
|
||||
println("Found "+aRecords.size() +" A records");
|
||||
|
||||
// Get A and AAAA records
|
||||
List<RecordEntity> ipRecords = cfDnsClient.sldInfo(zone, "www", RecordType.A, RecordType.AAAA);
|
||||
List<RecordEntity> ipRecords = cfDnsClient.recordGet(zone, "www", RecordType.A, RecordType.AAAA);
|
||||
ipRecords.
|
||||
|
||||
forEach(record ->System.out.
|
||||
forEach(record ->
|
||||
System.out.
|
||||
|
||||
println("IP Record: "+record.getContent()));
|
||||
```
|
||||
@@ -232,20 +249,25 @@ Process multiple DNS record operations (POST, PUT, PATCH, DELETE) in a single ba
|
||||
|
||||
- **Parameters**:
|
||||
- `ZoneEntity zone` - The target zone.
|
||||
- `List<RecordEntity> postRecords` - Records to create (nullable).
|
||||
- `List<RecordEntity> putRecords` - Records to fully replace (nullable).
|
||||
- `List<RecordEntity> patchRecords` - Records to partially update (nullable).
|
||||
- `List<RecordEntity> deleteRecords` - Records to delete (nullable).
|
||||
- `List<RecordEntity> postRecords` - Records to create (nullable). Can be built without IDs.
|
||||
- `List<RecordEntity> putRecords` - Records to fully replace (nullable). **Requires record IDs**.
|
||||
- `List<RecordEntity> patchRecords` - Records to partially update (nullable). **Requires record IDs**.
|
||||
- `List<RecordEntity> deleteRecords` - Records to delete (nullable). **Requires record IDs**.
|
||||
- **Returns**: A `BatchEntry` object containing the processed records.
|
||||
|
||||
**Important**: For UPDATE (PATCH), REPLACE (PUT), and DELETE operations, you must first retrieve the existing records to
|
||||
obtain their IDs.
|
||||
|
||||
#### Batch Create (POST)
|
||||
|
||||
Create new records, IDs are not required:
|
||||
|
||||
```java
|
||||
List<RecordEntity> newRecords = Arrays.asList(
|
||||
RecordEntity.build("api." + zone.getName(), RecordType.A, 60, "192.168.1.10"),
|
||||
RecordEntity.build("cdn." + zone.getName(), RecordType.A, 60, "192.168.1.11"),
|
||||
RecordEntity.build("mail." + zone.getName(), RecordType.A, 60, "192.168.1.12")
|
||||
);
|
||||
|
||||
BatchEntry result = cfDnsClient.recordBatch(zone, newRecords, null, null, null);
|
||||
System.out.
|
||||
|
||||
@@ -256,18 +278,30 @@ size() +" records.");
|
||||
|
||||
#### Batch Update (PATCH)
|
||||
|
||||
Partially update existing records. **Record IDs are required** - fetch them first:
|
||||
|
||||
```java
|
||||
// Fetch existing records and modify them
|
||||
List<RecordEntity> recordsToUpdate = Arrays.asList(
|
||||
cfDnsClient.sldInfo(zone, "api", RecordType.A),
|
||||
cfDnsClient.sldInfo(zone, "cdn", RecordType.A)
|
||||
);
|
||||
// Step 1: Fetch existing records to get their IDs
|
||||
List<RecordEntity> recordsToUpdate = new ArrayList<>();
|
||||
recordsToUpdate.
|
||||
|
||||
add(cfDnsClient.recordGet(zone, "api",RecordType.A).
|
||||
|
||||
get(0));
|
||||
recordsToUpdate.
|
||||
|
||||
add(cfDnsClient.recordGet(zone, "cdn",RecordType.A).
|
||||
|
||||
get(0));
|
||||
|
||||
// Step 2: Modify only the fields you want to update
|
||||
recordsToUpdate.
|
||||
|
||||
forEach(record ->record.
|
||||
|
||||
setContent("192.168.2.10"));
|
||||
|
||||
// Step 3: Send batch update request
|
||||
BatchEntry result = cfDnsClient.recordBatch(zone, null, null, recordsToUpdate, null);
|
||||
System.out.
|
||||
|
||||
@@ -278,14 +312,25 @@ size() +" records.");
|
||||
|
||||
#### Batch Replace (PUT)
|
||||
|
||||
Fully replace existing records. **Record IDs are required** - fetch them first:
|
||||
|
||||
```java
|
||||
// Fetch existing records and fully replace them
|
||||
List<RecordEntity> recordsToReplace = Arrays.asList(
|
||||
cfDnsClient.sldInfo(zone, "api", RecordType.A),
|
||||
cfDnsClient.sldInfo(zone, "cdn", RecordType.A)
|
||||
);
|
||||
// Step 1: Fetch existing records to get their IDs
|
||||
List<RecordEntity> recordsToReplace = new ArrayList<>();
|
||||
recordsToReplace.
|
||||
|
||||
add(cfDnsClient.recordGet(zone, "mail",RecordType.A).
|
||||
|
||||
get(0));
|
||||
recordsToReplace.
|
||||
|
||||
add(cfDnsClient.recordGet(zone, "cdn",RecordType.A).
|
||||
|
||||
get(0));
|
||||
|
||||
// Step 2: Modify all fields as needed (full replacement)
|
||||
recordsToReplace.
|
||||
|
||||
get(0).
|
||||
|
||||
setContent("192.168.3.10");
|
||||
@@ -294,7 +339,18 @@ recordsToReplace.
|
||||
get(0).
|
||||
|
||||
setTtl(120);
|
||||
recordsToReplace.
|
||||
|
||||
get(1).
|
||||
|
||||
setContent("192.168.3.11");
|
||||
recordsToReplace.
|
||||
|
||||
get(1).
|
||||
|
||||
setTtl(120);
|
||||
|
||||
// Step 3: Send batch replace request
|
||||
BatchEntry result = cfDnsClient.recordBatch(zone, null, recordsToReplace, null, null);
|
||||
System.out.
|
||||
|
||||
@@ -305,28 +361,177 @@ size() +" records.");
|
||||
|
||||
#### Batch Delete
|
||||
|
||||
Delete existing records. **Record IDs are required** - fetch them first:
|
||||
|
||||
```java
|
||||
List<RecordEntity> recordsToDelete = Arrays.asList(
|
||||
cfDnsClient.sldInfo(zone, "api", RecordType.A),
|
||||
cfDnsClient.sldInfo(zone, "mail", RecordType.A)
|
||||
);
|
||||
// Step 1: Fetch existing records to get their IDs
|
||||
List<RecordEntity> recordsToDelete = new ArrayList<>();
|
||||
recordsToDelete.
|
||||
|
||||
add(cfDnsClient.recordGet(zone, "cdn",RecordType.A).
|
||||
|
||||
get(0));
|
||||
recordsToDelete.
|
||||
|
||||
add(cfDnsClient.recordGet(zone, "mail",RecordType.A).
|
||||
|
||||
get(0));
|
||||
|
||||
// Step 2: Send batch delete request
|
||||
BatchEntry result = cfDnsClient.recordBatch(zone, null, null, null, recordsToDelete);
|
||||
System.out.
|
||||
|
||||
println("Deleted records.");
|
||||
println("Deleted "+recordsToDelete.size() +" records.");
|
||||
```
|
||||
|
||||
#### Combined Batch Operations
|
||||
|
||||
Combine multiple operations in a single batch request:
|
||||
|
||||
```java
|
||||
// You can combine multiple operations in a single batch request
|
||||
BatchEntry result = cfDnsClient.recordBatch(
|
||||
zone,
|
||||
newRecords, // POST
|
||||
putRecords, // PUT
|
||||
patchRecords, // PATCH
|
||||
deleteRecords // DELETE
|
||||
// Create new records (no IDs needed)
|
||||
List<RecordEntity> newRecords = Arrays.asList(
|
||||
RecordEntity.build("new-api." + zone.getName(), RecordType.A, 60, "192.168.1.100")
|
||||
);
|
||||
|
||||
// Fetch existing records for update/delete (IDs required)
|
||||
List<RecordEntity> recordsToUpdate = Arrays.asList(
|
||||
cfDnsClient.recordGet(zone, "existing-api", RecordType.A).get(0)
|
||||
);
|
||||
recordsToUpdate.
|
||||
|
||||
get(0).
|
||||
|
||||
setContent("192.168.1.200");
|
||||
|
||||
List<RecordEntity> recordsToDelete = Arrays.asList(
|
||||
cfDnsClient.recordGet(zone, "old-api", RecordType.A).get(0)
|
||||
);
|
||||
|
||||
// Execute all operations in a single batch request
|
||||
BatchEntry result = cfDnsClient.recordBatch(
|
||||
zone,
|
||||
newRecords, // POST - create new records
|
||||
null, // PUT - not used in this example
|
||||
recordsToUpdate, // PATCH - update existing records
|
||||
recordsToDelete // DELETE - remove records
|
||||
);
|
||||
|
||||
System.out.
|
||||
|
||||
println("Batch completed:");
|
||||
System.out.
|
||||
|
||||
println(" Created: "+result.getPosts().
|
||||
|
||||
size());
|
||||
System.out.
|
||||
|
||||
println(" Updated: "+result.getPatches().
|
||||
|
||||
size());
|
||||
System.out.
|
||||
|
||||
println(" Deleted: "+result.getDeletes().
|
||||
|
||||
size());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fluent API
|
||||
|
||||
The fluent API provides a chainable, readable interface for DNS operations. It's an alternative to the traditional API
|
||||
that reduces verbosity and improves code readability.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```java
|
||||
// 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
|
||||
RecordEntity updated = 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);
|
||||
```
|
||||
|
||||
### Advantages of Fluent API
|
||||
|
||||
- **More Readable**: The chain of method calls reads like natural language
|
||||
- **Less Verbose**: No need to pass zone objects between method calls
|
||||
- **Type Safe**: Full compile-time type checking
|
||||
- **IDE Friendly**: Excellent autocomplete support
|
||||
|
||||
### Complete Example
|
||||
|
||||
```java
|
||||
CfDnsClient client = new CfDnsClient("email@example.com", "yourApiKey");
|
||||
|
||||
// Create a new record
|
||||
client.
|
||||
|
||||
zone("example.com")
|
||||
.
|
||||
|
||||
record("api")
|
||||
.
|
||||
|
||||
create(RecordType.A, "192.168.100.1",60);
|
||||
|
||||
// Retrieve and verify
|
||||
List<RecordEntity> records = client.zone("example.com")
|
||||
.record("api", RecordType.A)
|
||||
.get();
|
||||
System.out.
|
||||
|
||||
println("IP: "+records.get(0).
|
||||
|
||||
getContent());
|
||||
|
||||
// Update the record
|
||||
client.
|
||||
|
||||
zone("example.com")
|
||||
.
|
||||
|
||||
record("api",RecordType.A)
|
||||
.
|
||||
|
||||
update("192.168.100.2");
|
||||
|
||||
// Clean up
|
||||
client.
|
||||
|
||||
zone("example.com")
|
||||
.
|
||||
|
||||
record("api")
|
||||
.
|
||||
|
||||
delete(RecordType.A);
|
||||
```
|
||||
|
||||
---
|
||||
@@ -335,21 +540,33 @@ BatchEntry result = cfDnsClient.recordBatch(
|
||||
|
||||
The `CfDnsClient` provides internal error-handling mechanisms through exceptions. For example:
|
||||
- `CloudflareApiException` is thrown for errors during API communication or invalid responses.
|
||||
- `CloudflareNotFoundException` is thrown when the requested single resource is not found, if enabled via the `emptyResultThrowsException` flag during initialization.
|
||||
- `CloudflareNotFoundException` is thrown when the requested resource (single or multiple) is not found, if enabled via
|
||||
the `emptyResultThrowsException` flag during initialization. **Default is `false`**, meaning empty results will be
|
||||
returned without throwing an exception.
|
||||
|
||||
To enable exception throwing for empty results:
|
||||
|
||||
```java
|
||||
CfDnsClient client = new CfDnsClient(true, "email@example.com", "yourApiKey");
|
||||
```
|
||||
|
||||
#### Example:
|
||||
|
||||
```java
|
||||
try {
|
||||
RecordEntity record = cfDnsClient.sldInfo(zone, "www", RecordType.A);
|
||||
System.out.println("Record IP: " + record.getContent());
|
||||
List<RecordEntity> records = cfDnsClient.recordGet(zone, "www", RecordType.A);
|
||||
System.out.
|
||||
|
||||
println("Record IP: "+records.get(0).
|
||||
|
||||
getContent());
|
||||
} catch (CloudflareApiException e) {
|
||||
if (e instanceof CloudflareNotFoundException) {
|
||||
log.warn("Sld not found: www");
|
||||
} else {
|
||||
log.error("Error while getting sld info of www", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import codes.thischwa.cf.model.ZoneEntity;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -32,7 +32,7 @@ public class CfClientPenTest {
|
||||
}
|
||||
|
||||
private CfDnsClient newClient() {
|
||||
return new CfDnsClient(API_EMAIL, API_KEY);
|
||||
return new CfDnsClient(true, API_EMAIL, API_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -40,14 +40,14 @@ public class CfClientPenTest {
|
||||
void testInvalidCredentialsShouldFail() {
|
||||
// Use syntactically valid but wrong credentials
|
||||
CfDnsClient badClient = new CfDnsClient("invalid@example.com", UUID.randomUUID().toString());
|
||||
assertThrows(CloudflareApiException.class, badClient::zoneListAll);
|
||||
assertThrows(CloudflareApiException.class, badClient::zoneList);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Malicious SLD inputs must not crash and should throw proper exception type")
|
||||
void testMaliciousSldPatternsDoNotSucceed() throws Exception {
|
||||
CfDnsClient client = newClient();
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||
|
||||
List<String> syntacticallyInvalidSlds =
|
||||
List.of("; rm -rf /", "| cat /etc/passwd", "`shutdown -h now`",
|
||||
@@ -58,11 +58,11 @@ public class CfClientPenTest {
|
||||
"doesnotexist", "abcdef12345", "unwahrscheinlich-" + System.currentTimeMillis());
|
||||
|
||||
for (String sld : syntacticallyInvalidSlds) {
|
||||
assertThrows(IllegalArgumentException.class, () -> client.sldListAll(zone, sld),
|
||||
assertThrows(IllegalArgumentException.class, () -> client.recordList(zone, sld),
|
||||
"Should throw IllegalArgumentException for invalid SLD '" + sld + "'");
|
||||
}
|
||||
for (String sld : syntacticallyValidOrNotAllowedFromCloudflare) {
|
||||
assertThrows(CloudflareNotFoundException.class, () -> client.sldListAll(zone, sld),
|
||||
assertThrows(CloudflareNotFoundException.class, () -> client.recordList(zone, sld),
|
||||
"Should throw CloudflareNotFoundException for valid but non-existing SLD '" + sld + "'");
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class CfClientPenTest {
|
||||
@DisplayName("Invalid record content and TTL boundaries must be rejected by API")
|
||||
void testInvalidRecordCreateInputsRejected() throws Exception {
|
||||
CfDnsClient client = newClient();
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||
|
||||
String sld = "pentest-" + System.currentTimeMillis();
|
||||
String fqdn = sld + "." + ZONE_STR;
|
||||
@@ -106,7 +106,7 @@ public class CfClientPenTest {
|
||||
@DisplayName("recordDeleteTypeIfExists must be safe on non-existing SLD and types")
|
||||
void testDeleteTypeIfExistsOnNonExistingIsSafe() throws Exception {
|
||||
CfDnsClient client = newClient();
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||
String randomSld = "nonexist-" + System.currentTimeMillis();
|
||||
// Should not throw even if nothing exists
|
||||
assertDoesNotThrow(
|
||||
|
||||
@@ -27,7 +27,7 @@ public class CfClientTest {
|
||||
private static final String API_EMAIL = System.getenv("API_EMAIL");
|
||||
private static final String API_KEY = System.getenv("API_KEY");
|
||||
|
||||
private final CfDnsClient client = new CfDnsClient(API_EMAIL, API_KEY);
|
||||
private final CfDnsClient client = new CfDnsClient(true, API_EMAIL, API_KEY);
|
||||
|
||||
@BeforeAll
|
||||
static void checkEnv() {
|
||||
@@ -37,18 +37,18 @@ public class CfClientTest {
|
||||
|
||||
@Test
|
||||
void testUnknownSld() throws Exception {
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
assertThrows(CloudflareNotFoundException.class, () -> client.sldInfo(zone, "unknown", RecordType.A));
|
||||
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||
assertThrows(CloudflareNotFoundException.class, () -> client.recordGet(zone, "unknown", RecordType.A));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddHost() throws Exception {
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||
client.recordDeleteTypeIfExists(zone, SLD_STR, RecordType.A, RecordType.AAAA);
|
||||
RecordEntity record = RecordEntity.build(SLD_STR, RecordType.A, TTL, "127.0.0.1");
|
||||
RecordEntity createdRecord = client.recordCreate(zone, record);
|
||||
assertNotNull(createdRecord.getId());
|
||||
assertEquals(SLD_STR, createdRecord.getName());
|
||||
assertEquals(SLD_STR, createdRecord.getSld());
|
||||
assertEquals(RecordType.A.getType(), createdRecord.getType());
|
||||
assertEquals(TTL, createdRecord.getTtl());
|
||||
assertEquals("127.0.0.1", createdRecord.getContent());
|
||||
@@ -56,12 +56,12 @@ public class CfClientTest {
|
||||
|
||||
client.recordDeleteTypeIfExists(zone, SLD_STR, RecordType.A);
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> client.sldInfo(zone, SLD_STR, RecordType.A));
|
||||
() -> client.recordGet(zone, SLD_STR, RecordType.A));
|
||||
|
||||
record = RecordEntity.build(SLD_STR + "." + ZONE_STR, RecordType.A, TTL, "127.1.0.1");
|
||||
createdRecord = client.recordCreate(zone, record);
|
||||
assertNotNull(createdRecord.getId());
|
||||
assertEquals(SLD_STR, createdRecord.getName());
|
||||
assertEquals(SLD_STR, createdRecord.getSld());
|
||||
assertEquals(RecordType.A.getType(), createdRecord.getType());
|
||||
assertEquals(TTL, createdRecord.getTtl());
|
||||
assertEquals("127.1.0.1", createdRecord.getContent());
|
||||
@@ -72,25 +72,25 @@ public class CfClientTest {
|
||||
|
||||
@Test
|
||||
void testZoneListAnlFailedSldList() throws Exception {
|
||||
List<ZoneEntity> zList = client.zoneListAll();
|
||||
List<ZoneEntity> zList = client.zoneList();
|
||||
assertEquals(1, zList.size());
|
||||
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> client.sldListAll(zList.get(0), "not-existing"));
|
||||
() -> client.recordList(zList.get(0), "not-existing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyResultThrowsException() throws Exception {
|
||||
List<ZoneEntity> zList = client.zoneListAll();
|
||||
List<ZoneEntity> zList = client.zoneList();
|
||||
CfDnsClient client = new CfDnsClient(true, API_EMAIL, API_KEY);
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> client.sldListAll(zList.get(0), "not-existing"));
|
||||
() -> client.recordList(zList.get(0), "not-existing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDns() throws Exception {
|
||||
// starting point: already existing zone 'mein-d-ns.de'
|
||||
ZoneEntity z = client.zoneInfo(ZONE_STR);
|
||||
ZoneEntity z = client.zoneGet(ZONE_STR);
|
||||
assertEquals("0a83dd6e7f8c46039f2517bbded8115e", z.getId());
|
||||
assertEquals("mein-d-ns.de", z.getName());
|
||||
assertEquals("active", z.getStatus());
|
||||
@@ -118,15 +118,15 @@ public class CfClientTest {
|
||||
createdRe1 =
|
||||
client.recordCreate(z, RecordEntity.build(domain, RecordType.A, TTL, "130.0.0.3"));
|
||||
assertNotNull(createdRe1.getId());
|
||||
assertEquals(randomSld, createdRe1.getName());
|
||||
assertEquals(randomSld, createdRe1.getSld());
|
||||
assertEquals(RecordType.A.getType(), createdRe1.getType());
|
||||
assertEquals(TTL, createdRe1.getTtl());
|
||||
assertEquals("130.0.0.3", createdRe1.getContent());
|
||||
assertNotNull(createdRe1.getCreatedOn());
|
||||
assertNotNull(createdRe1.getModifiedOn());
|
||||
|
||||
// verify sldInfo for A
|
||||
List<RecordEntity> aRecords = client.sldInfo(z, randomSld, RecordType.A);
|
||||
// verify recordGet for A
|
||||
List<RecordEntity> aRecords = client.recordGet(z, randomSld, RecordType.A);
|
||||
assertEquals(1, aRecords.size());
|
||||
r = aRecords.get(0);
|
||||
assertEquals("130.0.0.3", r.getContent());
|
||||
@@ -134,14 +134,14 @@ public class CfClientTest {
|
||||
// create AAAA record using recordCreateSld
|
||||
createdRe2 =
|
||||
client.recordCreateSld(z, randomSld, TTL, RecordType.AAAA, "2a0a:4cc0:c0:2e4::1");
|
||||
List<RecordEntity> aaaaRecords = client.sldInfo(z, randomSld, RecordType.AAAA);
|
||||
List<RecordEntity> aaaaRecords = client.recordGet(z, randomSld, RecordType.AAAA);
|
||||
assertEquals(1, aaaaRecords.size());
|
||||
r = aaaaRecords.get(0);
|
||||
assertEquals("2a0a:4cc0:c0:2e4::1", r.getContent());
|
||||
assertEquals(RecordType.AAAA.getType(), r.getType());
|
||||
|
||||
// test sldListAll
|
||||
List<RecordEntity> rList = client.sldListAll(z, randomSld);
|
||||
// test recordList
|
||||
List<RecordEntity> rList = client.recordList(z, randomSld);
|
||||
assertEquals(2, rList.size());
|
||||
for (RecordEntity re : rList) {
|
||||
if (Objects.equals(re.getType(), RecordType.A.getType())) {
|
||||
@@ -156,13 +156,13 @@ public class CfClientTest {
|
||||
// update AAAA record
|
||||
createdRe2.setContent("2a0a:4cc0:c0:2e4::2");
|
||||
client.recordUpdate(z, createdRe2);
|
||||
aaaaRecords = client.sldInfo(z, randomSld, RecordType.AAAA);
|
||||
aaaaRecords = client.recordGet(z, randomSld, RecordType.AAAA);
|
||||
assertEquals(1, aaaaRecords.size());
|
||||
r = aaaaRecords.get(0);
|
||||
assertEquals("2a0a:4cc0:c0:2e4::2", r.getContent());
|
||||
|
||||
// verify A record still intact
|
||||
aRecords = client.sldInfo(z, randomSld, RecordType.A);
|
||||
aRecords = client.recordGet(z, randomSld, RecordType.A);
|
||||
assertEquals(1, aRecords.size());
|
||||
r = aRecords.get(0);
|
||||
assertEquals("130.0.0.3", r.getContent());
|
||||
@@ -170,12 +170,12 @@ public class CfClientTest {
|
||||
// delete AAAA record and verify it's gone
|
||||
assertTrue(client.recordDelete(z, createdRe2));
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> client.sldInfo(z, randomSld, RecordType.AAAA));
|
||||
() -> client.recordGet(z, randomSld, RecordType.AAAA));
|
||||
|
||||
// delete A record using helper and verify it's gone
|
||||
client.recordDeleteTypeIfExists(z, randomSld, RecordType.A);
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> client.sldInfo(z, randomSld, RecordType.A));
|
||||
() -> client.recordGet(z, randomSld, RecordType.A));
|
||||
} finally {
|
||||
// cleanup in case of failures during test
|
||||
try {
|
||||
@@ -195,6 +195,14 @@ public class CfClientTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> new CfDnsClient("", "key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRecordEntityInvalidType() {
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> RecordEntity.build("id123", "example.com", "INVALID_TYPE", 60, "192.168.1.1"));
|
||||
assertTrue(exception.getMessage().contains("Invalid record type: INVALID_TYPE"));
|
||||
assertTrue(exception.getMessage().contains("Must be one of:"));
|
||||
}
|
||||
|
||||
private static final String IP_PREFIX = "130.0.0.";
|
||||
private static final String UPDATED_IP_PREFIX = "130.1.0.";
|
||||
|
||||
@@ -202,7 +210,7 @@ public class CfClientTest {
|
||||
@Test
|
||||
void testBatch() throws Exception {
|
||||
// starting point: already existing zone 'mein-d-ns.de'
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||
|
||||
List<String> sldNames = createSldNames();
|
||||
List<RecordEntity> initialRecords = createInitialRecords(sldNames);
|
||||
@@ -252,7 +260,7 @@ public class CfClientTest {
|
||||
|
||||
// Verify only the first 2 records
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<RecordEntity> records1 = client.sldInfo(zone, sldNames.get(i), RecordType.A);
|
||||
List<RecordEntity> records1 = client.recordGet(zone, sldNames.get(i), RecordType.A);
|
||||
assertEquals(1, records1.size());
|
||||
assertEquals(IP_PREFIX + (i + 1), records1.get(0).getContent());
|
||||
}
|
||||
@@ -262,7 +270,7 @@ public class CfClientTest {
|
||||
// Use first 2 records for PATCH
|
||||
List<RecordEntity> patchRecords = new ArrayList<>();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<RecordEntity> records = client.sldInfo(zone, sldNames.get(i), RecordType.A);
|
||||
List<RecordEntity> records = client.recordGet(zone, sldNames.get(i), RecordType.A);
|
||||
RecordEntity record = records.get(0);
|
||||
record.setContent(UPDATED_IP_PREFIX + (i + 1));
|
||||
patchRecords.add(record);
|
||||
@@ -272,7 +280,7 @@ public class CfClientTest {
|
||||
|
||||
// Verify both records were updated
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<RecordEntity> updatedRecords = client.sldInfo(zone, sldNames.get(i), RecordType.A);
|
||||
List<RecordEntity> updatedRecords = client.recordGet(zone, sldNames.get(i), RecordType.A);
|
||||
assertEquals(1, updatedRecords.size());
|
||||
assertEquals(UPDATED_IP_PREFIX + (i + 1), updatedRecords.get(0).getContent());
|
||||
}
|
||||
@@ -282,7 +290,7 @@ public class CfClientTest {
|
||||
// Delete first 2 records
|
||||
List<RecordEntity> deleteRecords = new ArrayList<>();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<RecordEntity> records = client.sldInfo(zone, sldNames.get(i), RecordType.A);
|
||||
List<RecordEntity> records = client.recordGet(zone, sldNames.get(i), RecordType.A);
|
||||
deleteRecords.add(records.get(0));
|
||||
}
|
||||
|
||||
@@ -292,7 +300,7 @@ public class CfClientTest {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
String sldName = sldNames.get(i);
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> client.sldInfo(zone, sldName, RecordType.A));
|
||||
() -> client.recordGet(zone, sldName, RecordType.A));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +316,7 @@ public class CfClientTest {
|
||||
// Now use PUT to replace them
|
||||
List<RecordEntity> putRecords = new ArrayList<>();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<RecordEntity> records = client.sldInfo(zone, sldNames.get(i), RecordType.A);
|
||||
List<RecordEntity> records = client.recordGet(zone, sldNames.get(i), RecordType.A);
|
||||
RecordEntity record = records.get(0);
|
||||
record.setContent(UPDATED_IP_PREFIX + (i + 1));
|
||||
putRecords.add(record);
|
||||
@@ -317,7 +325,7 @@ public class CfClientTest {
|
||||
|
||||
// Verify both records were updated
|
||||
for (int i = 0; i < 2; i++) {
|
||||
List<RecordEntity> updatedRecords = client.sldInfo(zone, sldNames.get(i), RecordType.A);
|
||||
List<RecordEntity> updatedRecords = client.recordGet(zone, sldNames.get(i), RecordType.A);
|
||||
assertEquals(1, updatedRecords.size());
|
||||
assertEquals(UPDATED_IP_PREFIX + (i + 1), updatedRecords.get(0).getContent());
|
||||
}
|
||||
@@ -325,9 +333,54 @@ public class CfClientTest {
|
||||
|
||||
private void assertValidBatchedRecord(RecordEntity batchedRecord, RecordEntity originalRecord) {
|
||||
assertNotNull(batchedRecord.getId());
|
||||
assertEquals(originalRecord.getName(), batchedRecord.getName());
|
||||
assertEquals(originalRecord.getSld(), batchedRecord.getSld());
|
||||
assertEquals(originalRecord.getType(), batchedRecord.getType());
|
||||
assertNotNull(batchedRecord.getCreatedOn());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFluentApi() throws Exception {
|
||||
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||
String fluentSld = "fluent-" + System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
// Test fluent create
|
||||
RecordEntity created = client.zone(ZONE_STR)
|
||||
.record(fluentSld)
|
||||
.create(RecordType.A, "192.168.100.1", TTL);
|
||||
|
||||
assertNotNull(created.getId());
|
||||
assertEquals(fluentSld, created.getSld());
|
||||
assertEquals("192.168.100.1", created.getContent());
|
||||
|
||||
// Test fluent get
|
||||
List<RecordEntity> records = client.zone(ZONE_STR)
|
||||
.record(fluentSld, RecordType.A)
|
||||
.get();
|
||||
|
||||
assertEquals(1, records.size());
|
||||
assertEquals("192.168.100.1", records.get(0).getContent());
|
||||
|
||||
// Test fluent update
|
||||
RecordEntity updated = client.zone(ZONE_STR)
|
||||
.record(fluentSld, RecordType.A)
|
||||
.update("192.168.100.2");
|
||||
|
||||
assertEquals("192.168.100.2", updated.getContent());
|
||||
|
||||
// Test fluent delete
|
||||
client.zone(ZONE_STR)
|
||||
.record(fluentSld)
|
||||
.delete(RecordType.A);
|
||||
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> client.zone(ZONE_STR).record(fluentSld, RecordType.A).get());
|
||||
|
||||
} finally {
|
||||
try {
|
||||
client.recordDeleteTypeIfExists(zone, fluentSld, RecordType.A);
|
||||
} catch (Exception e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import codes.thischwa.cf.model.AbstractResponse;
|
||||
import codes.thischwa.cf.model.RecordEntity;
|
||||
import codes.thischwa.cf.model.RecordMultipleResponse;
|
||||
import codes.thischwa.cf.model.RecordSingleResponse;
|
||||
import codes.thischwa.cf.model.ResponseResultInfo;
|
||||
import codes.thischwa.cf.model.ResultInfo;
|
||||
import java.util.ArrayList;
|
||||
@@ -29,6 +31,12 @@ class ResponseValidatorTest {
|
||||
@Mock
|
||||
private RecordMultipleResponse mockMultipleResponse;
|
||||
|
||||
@Mock
|
||||
private RecordSingleResponse mockSingleResponse;
|
||||
|
||||
@Mock
|
||||
private RecordEntity mockRecordEntity;
|
||||
|
||||
private ResponseValidator validatorWithException;
|
||||
private ResponseValidator validatorWithoutException;
|
||||
|
||||
@@ -93,4 +101,33 @@ class ResponseValidatorTest {
|
||||
|
||||
assertDoesNotThrow(() -> validatorWithoutException.validate(mockMultipleResponse, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSingleResultWithNullResultAndExceptionEnabled() {
|
||||
when(mockSingleResponse.getResponseResultInfo()).thenReturn(mockResultInfo);
|
||||
when(mockResultInfo.isSuccess()).thenReturn(true);
|
||||
when(mockSingleResponse.getResult()).thenReturn(null);
|
||||
|
||||
assertThrows(CloudflareNotFoundException.class,
|
||||
() -> validatorWithException.validate(mockSingleResponse, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSingleResultWithNullResultAndExceptionDisabled() {
|
||||
when(mockSingleResponse.getResponseResultInfo()).thenReturn(mockResultInfo);
|
||||
when(mockResultInfo.isSuccess()).thenReturn(true);
|
||||
// mockSingleResponse.getResult() returns null by default (no stubbing needed)
|
||||
|
||||
assertDoesNotThrow(() -> validatorWithoutException.validate(mockSingleResponse, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSingleResultWithValidResult() {
|
||||
when(mockSingleResponse.getResponseResultInfo()).thenReturn(mockResultInfo);
|
||||
when(mockResultInfo.isSuccess()).thenReturn(true);
|
||||
when(mockSingleResponse.getResult()).thenReturn(mockRecordEntity);
|
||||
|
||||
assertDoesNotThrow(() -> validatorWithException.validate(mockSingleResponse, false));
|
||||
assertDoesNotThrow(() -> validatorWithoutException.validate(mockSingleResponse, false));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user