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
|
## Changelog
|
||||||
|
|
||||||
- 0.2.0-beta-SNAPSHOT:
|
- 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
|
- CfClient#sldInfo must return multiple RecordEntries
|
||||||
- add a missing source jar
|
- add a missing source jar
|
||||||
- ResponseResultInfo#Errors: wrong object structure
|
- ResponseResultInfo#Errors: wrong object structure
|
||||||
@@ -68,6 +78,11 @@ The methods can be categorized as follows:
|
|||||||
- `Zone`: list, info
|
- `Zone`: list, info
|
||||||
- `Record`: list, info, create, update, delete
|
- `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 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).
|
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.
|
Retrieve all zones within the Cloudflare account.
|
||||||
|
|
||||||
- **Returns**: A list of `ZoneEntity` objects.
|
- **Returns**: A list of `ZoneEntity` objects.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
List<ZoneEntity> zones = cfDnsClient.zoneListAll();
|
List<ZoneEntity> zones = cfDnsClient.zoneList();
|
||||||
zones.forEach(zone -> System.out.println("Zone: " + zone.getName()));
|
zones.forEach(zone -> System.out.println("Zone: " + zone.getName()));
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `zoneInfo`
|
### `zoneGet`
|
||||||
|
|
||||||
Get detailed information about a specific zone by its name.
|
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.
|
- **Returns**: A `ZoneEntity` object.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
ZoneEntity zone = cfDnsClient.zoneInfo("example.com");
|
ZoneEntity zone = cfDnsClient.zoneGet("example.com");
|
||||||
System.out.println("Zone ID: " + zone.getId());
|
System.out.println("Zone ID: " + zone.getId());
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `sldListAll`
|
### `recordList`
|
||||||
|
|
||||||
Retrieve all records for a specific second-level domain (SLD) under a given zone.
|
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.
|
- **Returns**: A list of `RecordEntity` objects.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
List<RecordEntity> records = cfDnsClient.sldListAll(zone, "sld");
|
List<RecordEntity> records = cfDnsClient.recordList(zone, "sld");
|
||||||
records.forEach(record ->
|
records.
|
||||||
|
|
||||||
|
forEach(record ->
|
||||||
System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
|
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.
|
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
|
```java
|
||||||
// Get all records for a specific SLD
|
// Get all records for a specific SLD
|
||||||
List<RecordEntity> allRecords = cfDnsClient.sldInfo(zone, "www");
|
List<RecordEntity> allRecords = cfDnsClient.recordGet(zone, "www");
|
||||||
allRecords.
|
allRecords.
|
||||||
|
|
||||||
forEach(record ->
|
forEach(record ->
|
||||||
System.out.
|
System.out.
|
||||||
|
|
||||||
println("Type: "+record.getType() +", Content: "+record.
|
println("Type: "+record.getType()+", Content: "+record.
|
||||||
|
|
||||||
getContent())
|
getContent()));
|
||||||
);
|
|
||||||
|
|
||||||
// Get only A records
|
// Get only A records
|
||||||
List<RecordEntity> aRecords = cfDnsClient.sldInfo(zone, "www", RecordType.A);
|
List<RecordEntity> aRecords = cfDnsClient.recordGet(zone, "www", RecordType.A);
|
||||||
System.out.
|
System.out.
|
||||||
|
|
||||||
println("Found "+aRecords.size() +" A records");
|
println("Found "+aRecords.size() +" A records");
|
||||||
|
|
||||||
// Get A and AAAA 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.
|
ipRecords.
|
||||||
|
|
||||||
forEach(record ->System.out.
|
forEach(record ->
|
||||||
|
System.out.
|
||||||
|
|
||||||
println("IP Record: "+record.getContent()));
|
println("IP Record: "+record.getContent()));
|
||||||
```
|
```
|
||||||
@@ -232,20 +249,25 @@ Process multiple DNS record operations (POST, PUT, PATCH, DELETE) in a single ba
|
|||||||
|
|
||||||
- **Parameters**:
|
- **Parameters**:
|
||||||
- `ZoneEntity zone` - The target zone.
|
- `ZoneEntity zone` - The target zone.
|
||||||
- `List<RecordEntity> postRecords` - Records to create (nullable).
|
- `List<RecordEntity> postRecords` - Records to create (nullable). Can be built without IDs.
|
||||||
- `List<RecordEntity> putRecords` - Records to fully replace (nullable).
|
- `List<RecordEntity> putRecords` - Records to fully replace (nullable). **Requires record IDs**.
|
||||||
- `List<RecordEntity> patchRecords` - Records to partially update (nullable).
|
- `List<RecordEntity> patchRecords` - Records to partially update (nullable). **Requires record IDs**.
|
||||||
- `List<RecordEntity> deleteRecords` - Records to delete (nullable).
|
- `List<RecordEntity> deleteRecords` - Records to delete (nullable). **Requires record IDs**.
|
||||||
- **Returns**: A `BatchEntry` object containing the processed records.
|
- **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)
|
#### Batch Create (POST)
|
||||||
|
|
||||||
|
Create new records, IDs are not required:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
List<RecordEntity> newRecords = Arrays.asList(
|
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("cdn." + zone.getName(), RecordType.A, 60, "192.168.1.11"),
|
||||||
RecordEntity.build("mail." + zone.getName(), RecordType.A, 60, "192.168.1.12")
|
RecordEntity.build("mail." + zone.getName(), RecordType.A, 60, "192.168.1.12")
|
||||||
);
|
);
|
||||||
|
|
||||||
BatchEntry result = cfDnsClient.recordBatch(zone, newRecords, null, null, null);
|
BatchEntry result = cfDnsClient.recordBatch(zone, newRecords, null, null, null);
|
||||||
System.out.
|
System.out.
|
||||||
|
|
||||||
@@ -256,18 +278,30 @@ size() +" records.");
|
|||||||
|
|
||||||
#### Batch Update (PATCH)
|
#### Batch Update (PATCH)
|
||||||
|
|
||||||
|
Partially update existing records. **Record IDs are required** - fetch them first:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// Fetch existing records and modify them
|
// Step 1: Fetch existing records to get their IDs
|
||||||
List<RecordEntity> recordsToUpdate = Arrays.asList(
|
List<RecordEntity> recordsToUpdate = new ArrayList<>();
|
||||||
cfDnsClient.sldInfo(zone, "api", RecordType.A),
|
|
||||||
cfDnsClient.sldInfo(zone, "cdn", RecordType.A)
|
|
||||||
);
|
|
||||||
recordsToUpdate.
|
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.
|
forEach(record ->record.
|
||||||
|
|
||||||
setContent("192.168.2.10"));
|
setContent("192.168.2.10"));
|
||||||
|
|
||||||
|
// Step 3: Send batch update request
|
||||||
BatchEntry result = cfDnsClient.recordBatch(zone, null, null, recordsToUpdate, null);
|
BatchEntry result = cfDnsClient.recordBatch(zone, null, null, recordsToUpdate, null);
|
||||||
System.out.
|
System.out.
|
||||||
|
|
||||||
@@ -278,14 +312,25 @@ size() +" records.");
|
|||||||
|
|
||||||
#### Batch Replace (PUT)
|
#### Batch Replace (PUT)
|
||||||
|
|
||||||
|
Fully replace existing records. **Record IDs are required** - fetch them first:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// Fetch existing records and fully replace them
|
// Step 1: Fetch existing records to get their IDs
|
||||||
List<RecordEntity> recordsToReplace = Arrays.asList(
|
List<RecordEntity> recordsToReplace = new ArrayList<>();
|
||||||
cfDnsClient.sldInfo(zone, "api", RecordType.A),
|
|
||||||
cfDnsClient.sldInfo(zone, "cdn", RecordType.A)
|
|
||||||
);
|
|
||||||
recordsToReplace.
|
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).
|
get(0).
|
||||||
|
|
||||||
setContent("192.168.3.10");
|
setContent("192.168.3.10");
|
||||||
@@ -294,7 +339,18 @@ recordsToReplace.
|
|||||||
get(0).
|
get(0).
|
||||||
|
|
||||||
setTtl(120);
|
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);
|
BatchEntry result = cfDnsClient.recordBatch(zone, null, recordsToReplace, null, null);
|
||||||
System.out.
|
System.out.
|
||||||
|
|
||||||
@@ -305,28 +361,177 @@ size() +" records.");
|
|||||||
|
|
||||||
#### Batch Delete
|
#### Batch Delete
|
||||||
|
|
||||||
|
Delete existing records. **Record IDs are required** - fetch them first:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
List<RecordEntity> recordsToDelete = Arrays.asList(
|
// Step 1: Fetch existing records to get their IDs
|
||||||
cfDnsClient.sldInfo(zone, "api", RecordType.A),
|
List<RecordEntity> recordsToDelete = new ArrayList<>();
|
||||||
cfDnsClient.sldInfo(zone, "mail", RecordType.A)
|
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);
|
BatchEntry result = cfDnsClient.recordBatch(zone, null, null, null, recordsToDelete);
|
||||||
System.out.
|
System.out.
|
||||||
|
|
||||||
println("Deleted records.");
|
println("Deleted "+recordsToDelete.size() +" records.");
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Combined Batch Operations
|
#### Combined Batch Operations
|
||||||
|
|
||||||
|
Combine multiple operations in a single batch request:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// You can combine multiple operations in a single batch request
|
// Create new records (no IDs needed)
|
||||||
BatchEntry result = cfDnsClient.recordBatch(
|
List<RecordEntity> newRecords = Arrays.asList(
|
||||||
zone,
|
RecordEntity.build("new-api." + zone.getName(), RecordType.A, 60, "192.168.1.100")
|
||||||
newRecords, // POST
|
|
||||||
putRecords, // PUT
|
|
||||||
patchRecords, // PATCH
|
|
||||||
deleteRecords // DELETE
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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:
|
The `CfDnsClient` provides internal error-handling mechanisms through exceptions. For example:
|
||||||
- `CloudflareApiException` is thrown for errors during API communication or invalid responses.
|
- `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:
|
#### Example:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
try {
|
try {
|
||||||
RecordEntity record = cfDnsClient.sldInfo(zone, "www", RecordType.A);
|
List<RecordEntity> records = cfDnsClient.recordGet(zone, "www", RecordType.A);
|
||||||
System.out.println("Record IP: " + record.getContent());
|
System.out.
|
||||||
|
|
||||||
|
println("Record IP: "+records.get(0).
|
||||||
|
|
||||||
|
getContent());
|
||||||
} catch (CloudflareApiException e) {
|
} catch (CloudflareApiException e) {
|
||||||
if (e instanceof CloudflareNotFoundException) {
|
if (e instanceof CloudflareNotFoundException) {
|
||||||
log.warn("Sld not found: www");
|
log.warn("Sld not found: www");
|
||||||
} else {
|
} else {
|
||||||
log.error("Error while getting sld info of www", e);
|
log.error("Error while getting sld info of www", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -102,10 +102,16 @@ abstract class CfBasicHttpClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a DELETE request to the given endpoint and maps the response.
|
* 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));
|
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.
|
* 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,
|
<T extends AbstractResponse> T patchRequest(String endpoint,
|
||||||
Object requestPayload)
|
Object requestPayload,
|
||||||
|
Class<T> responseType)
|
||||||
throws CloudflareApiException {
|
throws CloudflareApiException {
|
||||||
HttpPatch request = new HttpPatch(buildUrl(endpoint));
|
HttpPatch request = new HttpPatch(buildUrl(endpoint));
|
||||||
setRequestPayload(request, requestPayload);
|
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;
|
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.AbstractResponse;
|
||||||
import codes.thischwa.cf.model.BatchEntry;
|
import codes.thischwa.cf.model.BatchEntry;
|
||||||
import codes.thischwa.cf.model.BatchResponse;
|
import codes.thischwa.cf.model.BatchResponse;
|
||||||
@@ -12,7 +14,6 @@ import codes.thischwa.cf.model.ZoneEntity;
|
|||||||
import codes.thischwa.cf.model.ZoneMultipleResponse;
|
import codes.thischwa.cf.model.ZoneMultipleResponse;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -29,19 +30,18 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
* "yourApiKey"
|
* "yourApiKey"
|
||||||
* );
|
* );
|
||||||
* // Retrieve a zone
|
* // Retrieve a zone
|
||||||
* ZoneEntity zone = cfDnsClient.zoneInfo("example.com");
|
* ZoneEntity zone = cfDnsClient.zoneGet("example.com");
|
||||||
* System.out.println("Zone ID: " + zone.getId());
|
* System.out.println("Zone ID: " + zone.getId());
|
||||||
* // Retrieve records of a subdomain
|
* // Retrieve records of a subdomain
|
||||||
* List<{@link RecordEntity}> records = cfDnsClient.sldListAll(zone, "sld");
|
* List<{@link RecordEntity}> records = cfDnsClient.recordGet(zone, "sld");
|
||||||
* records.forEach(record ->
|
* records.forEach(record ->
|
||||||
* System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
|
* System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
|
||||||
* );
|
* );
|
||||||
* // Create a record for the subdomain "api"
|
* // 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());
|
* System.out.println("Created Record ID: " + created.getId());
|
||||||
* </code></pre>
|
* </code></pre>
|
||||||
*/
|
*/
|
||||||
@Setter
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CfDnsClient extends CfBasicHttpClient {
|
public class CfDnsClient extends CfBasicHttpClient {
|
||||||
private static final String DEFAULT_BASEURL = "https://api.cloudflare.com/client/v4";
|
private static final String DEFAULT_BASEURL = "https://api.cloudflare.com/client/v4";
|
||||||
@@ -70,15 +70,15 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
* process.
|
* process.
|
||||||
*/
|
*/
|
||||||
public CfDnsClient(String baseUrl, String authEmail, String authKey) {
|
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}.
|
* Constructs a new instance of {@code CfDnsClient}.
|
||||||
*
|
*
|
||||||
* @param emptyResultThrowsException A boolean value indicating whether an exception should be
|
* @param emptyResultThrowsException A boolean value indicating whether an exception should be
|
||||||
* thrown when the result is empty, it's valid for 'list
|
* thrown when the result is empty. Applies to both single and
|
||||||
* requests' only. Default is true.
|
* multiple result requests. Default is false.
|
||||||
* @param authEmail The email address associated with the Cloudflare account,
|
* @param authEmail The email address associated with the Cloudflare account,
|
||||||
* used for authentication.
|
* used for authentication.
|
||||||
* @param authKey The API key of the Cloudflare account, used as part of the
|
* @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}.
|
* Constructs a new instance of {@code CfDnsClient}.
|
||||||
*
|
*
|
||||||
* @param emptyResultThrowsException A boolean value indicating whether an exception should be
|
* @param emptyResultThrowsException A boolean value indicating whether an exception should be
|
||||||
* thrown when the result is empty, it's valid for 'list
|
* thrown when the result is empty. Applies to both single and
|
||||||
* requests' only. Default is true.
|
* multiple result requests. Default is false.
|
||||||
* @param baseUrl The base URL for the Cloudflare API endpoint.
|
* @param baseUrl The base URL for the Cloudflare API endpoint.
|
||||||
* @param authEmail The email associated with the Cloudflare account for
|
* @param authEmail The email associated with the Cloudflare account for
|
||||||
* authentication.
|
* authentication.
|
||||||
@@ -110,14 +110,35 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
return sld + "." + zone.getName();
|
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.
|
* Retrieves a list of all zones from the Cloudflare API.
|
||||||
*
|
*
|
||||||
* @return A list of ZoneEntity objects representing the zones retrieved 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.
|
* @throws CloudflareApiException If an error occurs during the API request or response handling.
|
||||||
*/
|
*/
|
||||||
public List<ZoneEntity> zoneListAll() throws CloudflareApiException {
|
public List<ZoneEntity> zoneList() throws CloudflareApiException {
|
||||||
return zoneListAll(PagingRequest.defaultPaging());
|
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
|
* @throws CloudflareApiException if there is an error during the API request or response
|
||||||
* processing
|
* 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());
|
String endpoint = pagingRequest.addQueryString(CfRequest.ZONE_LIST.buildPath());
|
||||||
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
||||||
checkResponse(response);
|
checkResponse(response);
|
||||||
@@ -144,7 +165,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
* @throws CloudflareApiException If an error occurs while making the API request or processing
|
* @throws CloudflareApiException If an error occurs while making the API request or processing
|
||||||
* the response.
|
* the response.
|
||||||
*/
|
*/
|
||||||
public ZoneEntity zoneInfo(String name) throws CloudflareApiException {
|
public ZoneEntity zoneGet(String name) throws CloudflareApiException {
|
||||||
String endpoint = CfRequest.ZONE_INFO.buildPath(name);
|
String endpoint = CfRequest.ZONE_INFO.buildPath(name);
|
||||||
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
||||||
checkResponse(response, true);
|
checkResponse(response, true);
|
||||||
@@ -160,8 +181,8 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
* @return A list of {@code RecordEntity} associated with the desired SLD.
|
* @return A list of {@code RecordEntity} associated with the desired SLD.
|
||||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
||||||
*/
|
*/
|
||||||
public List<RecordEntity> sldListAll(ZoneEntity zone, String sld) throws CloudflareApiException {
|
public List<RecordEntity> recordList(ZoneEntity zone, String sld) throws CloudflareApiException {
|
||||||
return sldListAll(zone, sld, PagingRequest.defaultPaging());
|
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.
|
* @return A list of {@code RecordEntity} associated with the desired SLD.
|
||||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
* @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 {
|
throws CloudflareApiException {
|
||||||
String fqdn = buildFqdn(zone, sld);
|
String fqdn = buildFqdn(zone, sld);
|
||||||
String endpoint =
|
String endpoint =
|
||||||
@@ -194,12 +215,13 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
* @throws CloudflareNotFoundException if the specified SLD is not found in the zone
|
* @throws CloudflareNotFoundException if the specified SLD is not found in the zone
|
||||||
* @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API
|
* @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API
|
||||||
*/
|
*/
|
||||||
public List<RecordEntity> sldInfo(ZoneEntity zone, String sld) throws CloudflareApiException {
|
public List<RecordEntity> recordGet(ZoneEntity zone, String sld) throws CloudflareApiException {
|
||||||
return sldInfo(zone, sld, (RecordType[]) null);
|
return recordGet(zone, sld, (RecordType[]) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a list of DNS records for a given second-level domain (SLD) within a specific zone.
|
* 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 zone The zone entity containing information about the domain zone.
|
||||||
* @param sld The second-level domain (SLD) for which to retrieve DNS records.
|
* @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 CloudflareNotFoundException if the specified SLD is not found in the zone
|
||||||
* @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API
|
* @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 {
|
throws CloudflareApiException {
|
||||||
String fqdn = buildFqdn(zone, sld);
|
String fqdn = buildFqdn(zone, sld);
|
||||||
String endpoint = buildEndpointWithTypeFilters(zone.getId(), fqdn, types);
|
String endpoint = buildEndpointWithTypeFilters(zone.getId(), fqdn, types);
|
||||||
@@ -222,12 +244,11 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
if (types == null || types.length == 0) {
|
if (types == null || types.length == 0) {
|
||||||
return baseEndpoint;
|
return baseEndpoint;
|
||||||
}
|
}
|
||||||
StringBuilder queryParams = new StringBuilder();
|
StringBuilder endpoint = new StringBuilder(baseEndpoint);
|
||||||
for (RecordType type : types) {
|
for (RecordType type : types) {
|
||||||
queryParams.append("&");
|
endpoint.append("&type=").append(type);
|
||||||
queryParams.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());
|
String endpoint = CfRequest.RECORD_CREATE.buildPath(zone.getId());
|
||||||
RecordSingleResponse resp = postRequest(endpoint, rec, RecordSingleResponse.class);
|
RecordSingleResponse resp = postRequest(endpoint, rec, RecordSingleResponse.class);
|
||||||
checkResponse(resp);
|
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();
|
return resp.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,9 +318,9 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
public boolean recordDelete(ZoneEntity zone, RecordEntity rec) throws CloudflareApiException {
|
public boolean recordDelete(ZoneEntity zone, RecordEntity rec) throws CloudflareApiException {
|
||||||
boolean changed = recordDelete(zone, rec.getId());
|
boolean changed = recordDelete(zone, rec.getId());
|
||||||
if (changed) {
|
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 {
|
} 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;
|
return changed;
|
||||||
}
|
}
|
||||||
@@ -315,7 +336,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
*/
|
*/
|
||||||
public boolean recordDelete(ZoneEntity zone, String id) throws CloudflareApiException {
|
public boolean recordDelete(ZoneEntity zone, String id) throws CloudflareApiException {
|
||||||
String endpoint = CfRequest.RECORD_DELETE.buildPath(zone.getId(), id);
|
String endpoint = CfRequest.RECORD_DELETE.buildPath(zone.getId(), id);
|
||||||
RecordSingleResponse resp = deleteRequest(endpoint);
|
RecordSingleResponse resp = deleteRequest(endpoint, RecordSingleResponse.class);
|
||||||
checkResponse(resp);
|
checkResponse(resp);
|
||||||
log.debug("Record id#{} successful deleted.", id);
|
log.debug("Record id#{} successful deleted.", id);
|
||||||
return resp.getResult().getId().equals(id);
|
return resp.getResult().getId().equals(id);
|
||||||
@@ -336,9 +357,9 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
rec.setModifiedOn(null);
|
rec.setModifiedOn(null);
|
||||||
rec.setCreatedOn(null);
|
rec.setCreatedOn(null);
|
||||||
String endpoint = CfRequest.RECORD_UPDATE.buildPath(zone.getId(), rec.getId());
|
String endpoint = CfRequest.RECORD_UPDATE.buildPath(zone.getId(), rec.getId());
|
||||||
RecordSingleResponse resp = patchRequest(endpoint, rec);
|
RecordSingleResponse resp = patchRequest(endpoint, rec, RecordSingleResponse.class);
|
||||||
checkResponse(resp);
|
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();
|
return resp.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +377,7 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
String fqdn = buildFqdn(zone, sld);
|
String fqdn = buildFqdn(zone, sld);
|
||||||
List<RecordEntity> recs;
|
List<RecordEntity> recs;
|
||||||
try {
|
try {
|
||||||
recs = sldInfo(zone, sld, recordTypes);
|
recs = recordGet(zone, sld, recordTypes);
|
||||||
} catch (CloudflareNotFoundException e) {
|
} catch (CloudflareNotFoundException e) {
|
||||||
log.trace("No record of type {} found for domain {}.", recordTypes, fqdn);
|
log.trace("No record of type {} found for domain {}.", recordTypes, fqdn);
|
||||||
return;
|
return;
|
||||||
@@ -390,27 +411,16 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
BatchEntry batchEntry = new BatchEntry();
|
BatchEntry batchEntry = new BatchEntry();
|
||||||
// build 'clean' record entries
|
// build 'clean' record entries
|
||||||
if (postRecords != null) {
|
if (postRecords != null) {
|
||||||
List<RecordEntity> cleanedPosts = new ArrayList<>();
|
batchEntry.setPosts(cleanRecordsForPostOrPut(postRecords));
|
||||||
postRecords.forEach(
|
|
||||||
rec -> cleanedPosts.add(RecordEntity.build(rec.getId(), rec.getName(), rec.getType(), rec.getTtl(), rec.getContent())));
|
|
||||||
batchEntry.setPosts(cleanedPosts);
|
|
||||||
}
|
}
|
||||||
if (putRecords != null) {
|
if (putRecords != null) {
|
||||||
List<RecordEntity> cleanedPuts = new ArrayList<>();
|
batchEntry.setPuts(cleanRecordsForPostOrPut(putRecords));
|
||||||
putRecords.forEach(
|
|
||||||
rec -> cleanedPuts.add(RecordEntity.build(rec.getId(), rec.getName(), rec.getType(), rec.getTtl(), rec.getContent())));
|
|
||||||
batchEntry.setPuts(cleanedPuts);
|
|
||||||
}
|
}
|
||||||
if (patchRecords != null) {
|
if (patchRecords != null) {
|
||||||
List<RecordEntity> cleanedPatches = new ArrayList<>();
|
batchEntry.setPatches(cleanRecordsForPatch(patchRecords));
|
||||||
patchRecords.forEach(rec -> cleanedPatches.add(RecordEntity.build(rec.getId(), rec.getContent())));
|
|
||||||
batchEntry.setPatches(cleanedPatches);
|
|
||||||
}
|
}
|
||||||
if (deleteRecords != null) {
|
if (deleteRecords != null) {
|
||||||
List<RecordEntity> cleanedDeletes = new ArrayList<>();
|
batchEntry.setDeletes(cleanRecordsForDelete(deleteRecords));
|
||||||
deleteRecords.forEach(
|
|
||||||
rec -> cleanedDeletes.add(RecordEntity.build(rec.getId(), rec.getName(), rec.getType(), null, rec.getContent())));
|
|
||||||
batchEntry.setDeletes(cleanedDeletes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String endpoint = CfRequest.RECORD_BATCH.buildPath(zone.getId());
|
String endpoint = CfRequest.RECORD_BATCH.buildPath(zone.getId());
|
||||||
@@ -419,16 +429,40 @@ public class CfDnsClient extends CfBasicHttpClient {
|
|||||||
|
|
||||||
// set zone id
|
// set zone id
|
||||||
BatchEntry result = resp.getResult();
|
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) {
|
if (result.getPosts() != null) {
|
||||||
result.getPosts().forEach(rec -> rec.setZoneId(zone.getId()));
|
result.getPosts().forEach(rec -> rec.setZoneId(zoneId));
|
||||||
}
|
}
|
||||||
if (result.getPuts() != null) {
|
if (result.getPuts() != null) {
|
||||||
result.getPuts().forEach(rec -> rec.setZoneId(zone.getId()));
|
result.getPuts().forEach(rec -> rec.setZoneId(zoneId));
|
||||||
}
|
}
|
||||||
if (result.getPatches() != null) {
|
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 {
|
private void checkResponse(AbstractResponse resp) throws CloudflareApiException {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package codes.thischwa.cf;
|
package codes.thischwa.cf;
|
||||||
|
|
||||||
import codes.thischwa.cf.model.AbstractResponse;
|
import codes.thischwa.cf.model.AbstractResponse;
|
||||||
|
import codes.thischwa.cf.model.AbstractSingleResponse;
|
||||||
import codes.thischwa.cf.model.RecordMultipleResponse;
|
import codes.thischwa.cf.model.RecordMultipleResponse;
|
||||||
import codes.thischwa.cf.model.ResponseResultInfo;
|
import codes.thischwa.cf.model.ResponseResultInfo;
|
||||||
import java.util.stream.Collectors;
|
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
|
* <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
|
* metadata. If the response indicates failure, an exception is thrown with descriptive error
|
||||||
* messages.
|
* messages.
|
||||||
* <li>If a {@link RecordMultipleResponse} is used, it validates the number of results in the API
|
* <li>It validates the number of results in the API response payload to detect unexpected counts.
|
||||||
* response payload to detect unexpected counts. Depending on the parameter
|
* For {@link RecordMultipleResponse}, it checks if results are empty or if more than one result
|
||||||
* 'emptyResultThrowsException', an exception will be triggered or an empty result will be returned.
|
* 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>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
class ResponseValidator {
|
class ResponseValidator {
|
||||||
@@ -50,6 +53,10 @@ class ResponseValidator {
|
|||||||
if (emptyResultThrowsException && respMulti.getResultInfo().totalCount() == 0) {
|
if (emptyResultThrowsException && respMulti.getResultInfo().totalCount() == 0) {
|
||||||
throw new CloudflareNotFoundException("No result found");
|
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
|
@Data
|
||||||
public class PagingRequest {
|
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 page;
|
||||||
private int perPage;
|
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
|
* 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
|
* @return a default {@code PagingRequest} instance with predefined pagination parameters
|
||||||
*/
|
*/
|
||||||
public static PagingRequest defaultPaging() {
|
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.
|
* Retrieves the pagination parameters in a key-value map format.
|
||||||
*
|
*
|
||||||
* @return a map containing the pagination parameters, where the key "page" indicates the current
|
* @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() {
|
public Map<String, String> getPagingParams() {
|
||||||
return Map.of("page", String.valueOf(page), "perPage", String.valueOf(perPage));
|
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 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
|
* @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
|
* @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) {
|
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);
|
rec.setId(id);
|
||||||
return rec;
|
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.
|
* 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,
|
* @return the short name of the DNS record (substring before the first dot),
|
||||||
* or the full name if no dot is present.
|
* or the full name if no dot is present
|
||||||
*/
|
*/
|
||||||
public String getName() {
|
public String getSld() {
|
||||||
if (name != null) {
|
if (name == null) {
|
||||||
int pos = name.indexOf('.');
|
return null;
|
||||||
if (pos > 0) {
|
|
||||||
return name.substring(0, pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package codes.thischwa.cf;
|
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.RecordType;
|
||||||
import codes.thischwa.cf.model.ZoneEntity;
|
import codes.thischwa.cf.model.ZoneEntity;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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.BeforeAll;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -32,7 +32,7 @@ public class CfClientPenTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CfDnsClient newClient() {
|
private CfDnsClient newClient() {
|
||||||
return new CfDnsClient(API_EMAIL, API_KEY);
|
return new CfDnsClient(true, API_EMAIL, API_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -40,14 +40,14 @@ public class CfClientPenTest {
|
|||||||
void testInvalidCredentialsShouldFail() {
|
void testInvalidCredentialsShouldFail() {
|
||||||
// Use syntactically valid but wrong credentials
|
// Use syntactically valid but wrong credentials
|
||||||
CfDnsClient badClient = new CfDnsClient("invalid@example.com", UUID.randomUUID().toString());
|
CfDnsClient badClient = new CfDnsClient("invalid@example.com", UUID.randomUUID().toString());
|
||||||
assertThrows(CloudflareApiException.class, badClient::zoneListAll);
|
assertThrows(CloudflareApiException.class, badClient::zoneList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Malicious SLD inputs must not crash and should throw proper exception type")
|
@DisplayName("Malicious SLD inputs must not crash and should throw proper exception type")
|
||||||
void testMaliciousSldPatternsDoNotSucceed() throws Exception {
|
void testMaliciousSldPatternsDoNotSucceed() throws Exception {
|
||||||
CfDnsClient client = newClient();
|
CfDnsClient client = newClient();
|
||||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||||
|
|
||||||
List<String> syntacticallyInvalidSlds =
|
List<String> syntacticallyInvalidSlds =
|
||||||
List.of("; rm -rf /", "| cat /etc/passwd", "`shutdown -h now`",
|
List.of("; rm -rf /", "| cat /etc/passwd", "`shutdown -h now`",
|
||||||
@@ -58,11 +58,11 @@ public class CfClientPenTest {
|
|||||||
"doesnotexist", "abcdef12345", "unwahrscheinlich-" + System.currentTimeMillis());
|
"doesnotexist", "abcdef12345", "unwahrscheinlich-" + System.currentTimeMillis());
|
||||||
|
|
||||||
for (String sld : syntacticallyInvalidSlds) {
|
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 + "'");
|
"Should throw IllegalArgumentException for invalid SLD '" + sld + "'");
|
||||||
}
|
}
|
||||||
for (String sld : syntacticallyValidOrNotAllowedFromCloudflare) {
|
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 + "'");
|
"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")
|
@DisplayName("Invalid record content and TTL boundaries must be rejected by API")
|
||||||
void testInvalidRecordCreateInputsRejected() throws Exception {
|
void testInvalidRecordCreateInputsRejected() throws Exception {
|
||||||
CfDnsClient client = newClient();
|
CfDnsClient client = newClient();
|
||||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||||
|
|
||||||
String sld = "pentest-" + System.currentTimeMillis();
|
String sld = "pentest-" + System.currentTimeMillis();
|
||||||
String fqdn = sld + "." + ZONE_STR;
|
String fqdn = sld + "." + ZONE_STR;
|
||||||
@@ -106,7 +106,7 @@ public class CfClientPenTest {
|
|||||||
@DisplayName("recordDeleteTypeIfExists must be safe on non-existing SLD and types")
|
@DisplayName("recordDeleteTypeIfExists must be safe on non-existing SLD and types")
|
||||||
void testDeleteTypeIfExistsOnNonExistingIsSafe() throws Exception {
|
void testDeleteTypeIfExistsOnNonExistingIsSafe() throws Exception {
|
||||||
CfDnsClient client = newClient();
|
CfDnsClient client = newClient();
|
||||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||||
String randomSld = "nonexist-" + System.currentTimeMillis();
|
String randomSld = "nonexist-" + System.currentTimeMillis();
|
||||||
// Should not throw even if nothing exists
|
// Should not throw even if nothing exists
|
||||||
assertDoesNotThrow(
|
assertDoesNotThrow(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class CfClientTest {
|
|||||||
private static final String API_EMAIL = System.getenv("API_EMAIL");
|
private static final String API_EMAIL = System.getenv("API_EMAIL");
|
||||||
private static final String API_KEY = System.getenv("API_KEY");
|
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
|
@BeforeAll
|
||||||
static void checkEnv() {
|
static void checkEnv() {
|
||||||
@@ -37,18 +37,18 @@ public class CfClientTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testUnknownSld() throws Exception {
|
void testUnknownSld() throws Exception {
|
||||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
ZoneEntity zone = client.zoneGet(ZONE_STR);
|
||||||
assertThrows(CloudflareNotFoundException.class, () -> client.sldInfo(zone, "unknown", RecordType.A));
|
assertThrows(CloudflareNotFoundException.class, () -> client.recordGet(zone, "unknown", RecordType.A));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAddHost() throws Exception {
|
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);
|
client.recordDeleteTypeIfExists(zone, SLD_STR, RecordType.A, RecordType.AAAA);
|
||||||
RecordEntity record = RecordEntity.build(SLD_STR, RecordType.A, TTL, "127.0.0.1");
|
RecordEntity record = RecordEntity.build(SLD_STR, RecordType.A, TTL, "127.0.0.1");
|
||||||
RecordEntity createdRecord = client.recordCreate(zone, record);
|
RecordEntity createdRecord = client.recordCreate(zone, record);
|
||||||
assertNotNull(createdRecord.getId());
|
assertNotNull(createdRecord.getId());
|
||||||
assertEquals(SLD_STR, createdRecord.getName());
|
assertEquals(SLD_STR, createdRecord.getSld());
|
||||||
assertEquals(RecordType.A.getType(), createdRecord.getType());
|
assertEquals(RecordType.A.getType(), createdRecord.getType());
|
||||||
assertEquals(TTL, createdRecord.getTtl());
|
assertEquals(TTL, createdRecord.getTtl());
|
||||||
assertEquals("127.0.0.1", createdRecord.getContent());
|
assertEquals("127.0.0.1", createdRecord.getContent());
|
||||||
@@ -56,12 +56,12 @@ public class CfClientTest {
|
|||||||
|
|
||||||
client.recordDeleteTypeIfExists(zone, SLD_STR, RecordType.A);
|
client.recordDeleteTypeIfExists(zone, SLD_STR, RecordType.A);
|
||||||
assertThrows(CloudflareNotFoundException.class,
|
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");
|
record = RecordEntity.build(SLD_STR + "." + ZONE_STR, RecordType.A, TTL, "127.1.0.1");
|
||||||
createdRecord = client.recordCreate(zone, record);
|
createdRecord = client.recordCreate(zone, record);
|
||||||
assertNotNull(createdRecord.getId());
|
assertNotNull(createdRecord.getId());
|
||||||
assertEquals(SLD_STR, createdRecord.getName());
|
assertEquals(SLD_STR, createdRecord.getSld());
|
||||||
assertEquals(RecordType.A.getType(), createdRecord.getType());
|
assertEquals(RecordType.A.getType(), createdRecord.getType());
|
||||||
assertEquals(TTL, createdRecord.getTtl());
|
assertEquals(TTL, createdRecord.getTtl());
|
||||||
assertEquals("127.1.0.1", createdRecord.getContent());
|
assertEquals("127.1.0.1", createdRecord.getContent());
|
||||||
@@ -72,25 +72,25 @@ public class CfClientTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testZoneListAnlFailedSldList() throws Exception {
|
void testZoneListAnlFailedSldList() throws Exception {
|
||||||
List<ZoneEntity> zList = client.zoneListAll();
|
List<ZoneEntity> zList = client.zoneList();
|
||||||
assertEquals(1, zList.size());
|
assertEquals(1, zList.size());
|
||||||
|
|
||||||
assertThrows(CloudflareNotFoundException.class,
|
assertThrows(CloudflareNotFoundException.class,
|
||||||
() -> client.sldListAll(zList.get(0), "not-existing"));
|
() -> client.recordList(zList.get(0), "not-existing"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmptyResultThrowsException() throws Exception {
|
void testEmptyResultThrowsException() throws Exception {
|
||||||
List<ZoneEntity> zList = client.zoneListAll();
|
List<ZoneEntity> zList = client.zoneList();
|
||||||
CfDnsClient client = new CfDnsClient(true, API_EMAIL, API_KEY);
|
CfDnsClient client = new CfDnsClient(true, API_EMAIL, API_KEY);
|
||||||
assertThrows(CloudflareNotFoundException.class,
|
assertThrows(CloudflareNotFoundException.class,
|
||||||
() -> client.sldListAll(zList.get(0), "not-existing"));
|
() -> client.recordList(zList.get(0), "not-existing"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDns() throws Exception {
|
void testDns() throws Exception {
|
||||||
// starting point: already existing zone 'mein-d-ns.de'
|
// 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("0a83dd6e7f8c46039f2517bbded8115e", z.getId());
|
||||||
assertEquals("mein-d-ns.de", z.getName());
|
assertEquals("mein-d-ns.de", z.getName());
|
||||||
assertEquals("active", z.getStatus());
|
assertEquals("active", z.getStatus());
|
||||||
@@ -118,15 +118,15 @@ public class CfClientTest {
|
|||||||
createdRe1 =
|
createdRe1 =
|
||||||
client.recordCreate(z, RecordEntity.build(domain, RecordType.A, TTL, "130.0.0.3"));
|
client.recordCreate(z, RecordEntity.build(domain, RecordType.A, TTL, "130.0.0.3"));
|
||||||
assertNotNull(createdRe1.getId());
|
assertNotNull(createdRe1.getId());
|
||||||
assertEquals(randomSld, createdRe1.getName());
|
assertEquals(randomSld, createdRe1.getSld());
|
||||||
assertEquals(RecordType.A.getType(), createdRe1.getType());
|
assertEquals(RecordType.A.getType(), createdRe1.getType());
|
||||||
assertEquals(TTL, createdRe1.getTtl());
|
assertEquals(TTL, createdRe1.getTtl());
|
||||||
assertEquals("130.0.0.3", createdRe1.getContent());
|
assertEquals("130.0.0.3", createdRe1.getContent());
|
||||||
assertNotNull(createdRe1.getCreatedOn());
|
assertNotNull(createdRe1.getCreatedOn());
|
||||||
assertNotNull(createdRe1.getModifiedOn());
|
assertNotNull(createdRe1.getModifiedOn());
|
||||||
|
|
||||||
// verify sldInfo for A
|
// verify recordGet for A
|
||||||
List<RecordEntity> aRecords = client.sldInfo(z, randomSld, RecordType.A);
|
List<RecordEntity> aRecords = client.recordGet(z, randomSld, RecordType.A);
|
||||||
assertEquals(1, aRecords.size());
|
assertEquals(1, aRecords.size());
|
||||||
r = aRecords.get(0);
|
r = aRecords.get(0);
|
||||||
assertEquals("130.0.0.3", r.getContent());
|
assertEquals("130.0.0.3", r.getContent());
|
||||||
@@ -134,14 +134,14 @@ public class CfClientTest {
|
|||||||
// create AAAA record using recordCreateSld
|
// create AAAA record using recordCreateSld
|
||||||
createdRe2 =
|
createdRe2 =
|
||||||
client.recordCreateSld(z, randomSld, TTL, RecordType.AAAA, "2a0a:4cc0:c0:2e4::1");
|
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());
|
assertEquals(1, aaaaRecords.size());
|
||||||
r = aaaaRecords.get(0);
|
r = aaaaRecords.get(0);
|
||||||
assertEquals("2a0a:4cc0:c0:2e4::1", r.getContent());
|
assertEquals("2a0a:4cc0:c0:2e4::1", r.getContent());
|
||||||
assertEquals(RecordType.AAAA.getType(), r.getType());
|
assertEquals(RecordType.AAAA.getType(), r.getType());
|
||||||
|
|
||||||
// test sldListAll
|
// test recordList
|
||||||
List<RecordEntity> rList = client.sldListAll(z, randomSld);
|
List<RecordEntity> rList = client.recordList(z, randomSld);
|
||||||
assertEquals(2, rList.size());
|
assertEquals(2, rList.size());
|
||||||
for (RecordEntity re : rList) {
|
for (RecordEntity re : rList) {
|
||||||
if (Objects.equals(re.getType(), RecordType.A.getType())) {
|
if (Objects.equals(re.getType(), RecordType.A.getType())) {
|
||||||
@@ -156,13 +156,13 @@ public class CfClientTest {
|
|||||||
// update AAAA record
|
// update AAAA record
|
||||||
createdRe2.setContent("2a0a:4cc0:c0:2e4::2");
|
createdRe2.setContent("2a0a:4cc0:c0:2e4::2");
|
||||||
client.recordUpdate(z, createdRe2);
|
client.recordUpdate(z, createdRe2);
|
||||||
aaaaRecords = client.sldInfo(z, randomSld, RecordType.AAAA);
|
aaaaRecords = client.recordGet(z, randomSld, RecordType.AAAA);
|
||||||
assertEquals(1, aaaaRecords.size());
|
assertEquals(1, aaaaRecords.size());
|
||||||
r = aaaaRecords.get(0);
|
r = aaaaRecords.get(0);
|
||||||
assertEquals("2a0a:4cc0:c0:2e4::2", r.getContent());
|
assertEquals("2a0a:4cc0:c0:2e4::2", r.getContent());
|
||||||
|
|
||||||
// verify A record still intact
|
// verify A record still intact
|
||||||
aRecords = client.sldInfo(z, randomSld, RecordType.A);
|
aRecords = client.recordGet(z, randomSld, RecordType.A);
|
||||||
assertEquals(1, aRecords.size());
|
assertEquals(1, aRecords.size());
|
||||||
r = aRecords.get(0);
|
r = aRecords.get(0);
|
||||||
assertEquals("130.0.0.3", r.getContent());
|
assertEquals("130.0.0.3", r.getContent());
|
||||||
@@ -170,12 +170,12 @@ public class CfClientTest {
|
|||||||
// delete AAAA record and verify it's gone
|
// delete AAAA record and verify it's gone
|
||||||
assertTrue(client.recordDelete(z, createdRe2));
|
assertTrue(client.recordDelete(z, createdRe2));
|
||||||
assertThrows(CloudflareNotFoundException.class,
|
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
|
// delete A record using helper and verify it's gone
|
||||||
client.recordDeleteTypeIfExists(z, randomSld, RecordType.A);
|
client.recordDeleteTypeIfExists(z, randomSld, RecordType.A);
|
||||||
assertThrows(CloudflareNotFoundException.class,
|
assertThrows(CloudflareNotFoundException.class,
|
||||||
() -> client.sldInfo(z, randomSld, RecordType.A));
|
() -> client.recordGet(z, randomSld, RecordType.A));
|
||||||
} finally {
|
} finally {
|
||||||
// cleanup in case of failures during test
|
// cleanup in case of failures during test
|
||||||
try {
|
try {
|
||||||
@@ -195,6 +195,14 @@ public class CfClientTest {
|
|||||||
assertThrows(IllegalArgumentException.class, () -> new CfDnsClient("", "key"));
|
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 IP_PREFIX = "130.0.0.";
|
||||||
private static final String UPDATED_IP_PREFIX = "130.1.0.";
|
private static final String UPDATED_IP_PREFIX = "130.1.0.";
|
||||||
|
|
||||||
@@ -202,7 +210,7 @@ public class CfClientTest {
|
|||||||
@Test
|
@Test
|
||||||
void testBatch() throws Exception {
|
void testBatch() throws Exception {
|
||||||
// starting point: already existing zone 'mein-d-ns.de'
|
// 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<String> sldNames = createSldNames();
|
||||||
List<RecordEntity> initialRecords = createInitialRecords(sldNames);
|
List<RecordEntity> initialRecords = createInitialRecords(sldNames);
|
||||||
@@ -252,7 +260,7 @@ public class CfClientTest {
|
|||||||
|
|
||||||
// Verify only the first 2 records
|
// Verify only the first 2 records
|
||||||
for (int i = 0; i < 2; i++) {
|
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(1, records1.size());
|
||||||
assertEquals(IP_PREFIX + (i + 1), records1.get(0).getContent());
|
assertEquals(IP_PREFIX + (i + 1), records1.get(0).getContent());
|
||||||
}
|
}
|
||||||
@@ -262,7 +270,7 @@ public class CfClientTest {
|
|||||||
// Use first 2 records for PATCH
|
// Use first 2 records for PATCH
|
||||||
List<RecordEntity> patchRecords = new ArrayList<>();
|
List<RecordEntity> patchRecords = new ArrayList<>();
|
||||||
for (int i = 0; i < 2; i++) {
|
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);
|
RecordEntity record = records.get(0);
|
||||||
record.setContent(UPDATED_IP_PREFIX + (i + 1));
|
record.setContent(UPDATED_IP_PREFIX + (i + 1));
|
||||||
patchRecords.add(record);
|
patchRecords.add(record);
|
||||||
@@ -272,7 +280,7 @@ public class CfClientTest {
|
|||||||
|
|
||||||
// Verify both records were updated
|
// Verify both records were updated
|
||||||
for (int i = 0; i < 2; i++) {
|
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(1, updatedRecords.size());
|
||||||
assertEquals(UPDATED_IP_PREFIX + (i + 1), updatedRecords.get(0).getContent());
|
assertEquals(UPDATED_IP_PREFIX + (i + 1), updatedRecords.get(0).getContent());
|
||||||
}
|
}
|
||||||
@@ -282,7 +290,7 @@ public class CfClientTest {
|
|||||||
// Delete first 2 records
|
// Delete first 2 records
|
||||||
List<RecordEntity> deleteRecords = new ArrayList<>();
|
List<RecordEntity> deleteRecords = new ArrayList<>();
|
||||||
for (int i = 0; i < 2; i++) {
|
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));
|
deleteRecords.add(records.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +300,7 @@ public class CfClientTest {
|
|||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
String sldName = sldNames.get(i);
|
String sldName = sldNames.get(i);
|
||||||
assertThrows(CloudflareNotFoundException.class,
|
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
|
// Now use PUT to replace them
|
||||||
List<RecordEntity> putRecords = new ArrayList<>();
|
List<RecordEntity> putRecords = new ArrayList<>();
|
||||||
for (int i = 0; i < 2; i++) {
|
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);
|
RecordEntity record = records.get(0);
|
||||||
record.setContent(UPDATED_IP_PREFIX + (i + 1));
|
record.setContent(UPDATED_IP_PREFIX + (i + 1));
|
||||||
putRecords.add(record);
|
putRecords.add(record);
|
||||||
@@ -317,7 +325,7 @@ public class CfClientTest {
|
|||||||
|
|
||||||
// Verify both records were updated
|
// Verify both records were updated
|
||||||
for (int i = 0; i < 2; i++) {
|
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(1, updatedRecords.size());
|
||||||
assertEquals(UPDATED_IP_PREFIX + (i + 1), updatedRecords.get(0).getContent());
|
assertEquals(UPDATED_IP_PREFIX + (i + 1), updatedRecords.get(0).getContent());
|
||||||
}
|
}
|
||||||
@@ -325,9 +333,54 @@ public class CfClientTest {
|
|||||||
|
|
||||||
private void assertValidBatchedRecord(RecordEntity batchedRecord, RecordEntity originalRecord) {
|
private void assertValidBatchedRecord(RecordEntity batchedRecord, RecordEntity originalRecord) {
|
||||||
assertNotNull(batchedRecord.getId());
|
assertNotNull(batchedRecord.getId());
|
||||||
assertEquals(originalRecord.getName(), batchedRecord.getName());
|
assertEquals(originalRecord.getSld(), batchedRecord.getSld());
|
||||||
assertEquals(originalRecord.getType(), batchedRecord.getType());
|
assertEquals(originalRecord.getType(), batchedRecord.getType());
|
||||||
assertNotNull(batchedRecord.getCreatedOn());
|
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 static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import codes.thischwa.cf.model.AbstractResponse;
|
import codes.thischwa.cf.model.AbstractResponse;
|
||||||
|
import codes.thischwa.cf.model.RecordEntity;
|
||||||
import codes.thischwa.cf.model.RecordMultipleResponse;
|
import codes.thischwa.cf.model.RecordMultipleResponse;
|
||||||
|
import codes.thischwa.cf.model.RecordSingleResponse;
|
||||||
import codes.thischwa.cf.model.ResponseResultInfo;
|
import codes.thischwa.cf.model.ResponseResultInfo;
|
||||||
import codes.thischwa.cf.model.ResultInfo;
|
import codes.thischwa.cf.model.ResultInfo;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -29,6 +31,12 @@ class ResponseValidatorTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private RecordMultipleResponse mockMultipleResponse;
|
private RecordMultipleResponse mockMultipleResponse;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RecordSingleResponse mockSingleResponse;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RecordEntity mockRecordEntity;
|
||||||
|
|
||||||
private ResponseValidator validatorWithException;
|
private ResponseValidator validatorWithException;
|
||||||
private ResponseValidator validatorWithoutException;
|
private ResponseValidator validatorWithoutException;
|
||||||
|
|
||||||
@@ -93,4 +101,33 @@ class ResponseValidatorTest {
|
|||||||
|
|
||||||
assertDoesNotThrow(() -> validatorWithoutException.validate(mockMultipleResponse, true));
|
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