13 Commits

Author SHA1 Message Date
thischwa 62b86ca22f [maven-release-plugin] prepare release v0.3.0 2026-02-15 17:33:34 +01:00
thischwa f12f5d6db1 Update .gitlab-ci.yml: Adjust apidocs path, and enhance documentation in ResultInfo and CfDnsClientBuilder. 2026-01-25 18:59:02 +01:00
thischwa c435a80966 Update pom.xml: Bump JUnit to 5.14.2, Mockito to 5.21.0, and Maven Release Plugin to 3.3.1. 2026-01-25 18:13:03 +01:00
thischwa 1e8fe53891 Update pom.xml: Bump dependency and plugin versions (Jackson: 2.19.1, HttpClient: 5.5.1, JUnit: 5.12.3, Maven Javadoc Plugin: 3.11.2, Release Plugin: 3.1.2, Jacoco Plugin: 0.8.13). 2026-01-25 17:57:27 +01:00
thischwa 10ad4bd7f3 Update version in pom.xml from 0.2.1-SNAPSHOT to 0.3.0-SNAPSHOT. 2026-01-25 14:39:49 +01:00
thischwa 84a454f656 fixed changelog 2026-01-25 14:30:59 +01:00
thischwa 4a3ec42fa5 reducing code smells 2026-01-23 18:32:54 +01:00
thischwa 9e09b12dc3 Update README.md: Remove duplicate "de-lombok the source jar" entry in changelog. 2026-01-23 17:55:44 +01:00
thischwa d0802fc01d Update pom.xml: Bump Checkstyle to 12.3.0, configure maven-jar-plugin with version 3.4.2, and adjust Checkstyle properties. 2026-01-23 17:40:53 +01:00
thischwa 607b634b35 issue #11 - Refactor CfDnsClient with a simplified CfDnsClientBuilder for authentication and configuration. Update tests and README for new builder. Added authtification with apiToken 2026-01-23 17:26:58 +01:00
thischwa b593ca7311 issue #11 -Implement authentication refactor: Add CfAuth interface, ApiTokenAuth and EmailKeyAuth implementations, and CfAuthBuilder for flexible authentication in CfDnsClient. Update tests and adjust related classes for compatibility. 2026-01-23 15:36:17 +01:00
thischwa 773573bd39 Update README.md: release 0.2.0 details and note de-lomboked source jar. 2026-01-19 18:21:45 +01:00
thischwa b890ae9d17 [maven-release-plugin] prepare for next development iteration 2026-01-19 18:03:50 +01:00
13 changed files with 346 additions and 151 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ build:
- mkdir public
- cp -rv docs/* public/
- mkdir public/apidocs
- cp -rv target/apidocs public/
- cp -rv target/reports/apidocs public/
artifacts:
paths:
- target/surefire-reports/*.xml
+26 -9
View File
@@ -29,7 +29,7 @@ This guide comes without any warranty. Use at your own risk. The author is not r
The project has its own maven repository. It can be added to the `pom.xml`:
```xml
```xml<p>
<repositories>
<repository>
<id>gitlab-cloudflare</id>
@@ -49,14 +49,18 @@ The dependency is:
## Changelog
- 0.2.0-beta-SNAPSHOT:
- **New Fluent API**: Added chainable method interface for more readable DNS operations (
`client.zone().record()...`)
- 0.3.0-SNAPSHOT:
- **Breaking Change**:
- **New Fluent API**: Changed the initialization of the client(`new CfDnsClientBuilder().withApiTokenAuth("your-api-token").build()`)
- Authentication with API token.
- 0.2.0:
- **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`
- RecordEntity getter methods renamed for clarity: `getName()` → `getSld()`
- **New Fluent API**: Changed the initialization of the client(`new CfDnsClientBuilder().withApiTokenAuth("your-api-token").build()`) and added chainable method interface for more readable DNS operations (
`client.zone().record()...`)
- 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
@@ -88,10 +92,18 @@ the [javadoc of the CfDnsClient](https://cloudflaredns-java-f4ee3a.gitlab.io/api
### Instantiation of `CfDnsClient`
#### With API Token (recommended):
```java
CfDnsClient cfDnsClient = new CfDnsClient(
"email@example.com", "yourApiKey"
);
CfDnsClient cfDnsClient = new CfDnsClientBuilder()
.withApiTokenAuth("your-api-token")
.build();
```
#### With Email/Key (legacy):
```java
CfDnsClient cfDnsClient = new CfDnsClientBuilder()
.withEmailKeyAuth("email@example.com", "yourApiKey")
.build();
```
### `zoneList`
@@ -389,7 +401,9 @@ client.zone("example.com")
### Complete Example
```java
CfDnsClient client = new CfDnsClient("email@example.com", "yourApiKey");
CfDnsClient client = new CfDnsClientBuilder()
.withApiTokenAuth("your-api-token")
.build();
// Create a new record
client.zone("example.com")
@@ -424,7 +438,10 @@ The `CfDnsClient` provides internal error-handling mechanisms through exceptions
To enable exception throwing for empty results:
```java
CfDnsClient client = new CfDnsClient(true, "email@example.com", "yourApiKey");
CfDnsClient client = new CfDnsClientBuilder()
.withApiTokenAuth("your-api-token")
.withEmptyResultThrowsException(true)
.build();
```
## Example:
+14 -11
View File
@@ -4,7 +4,7 @@
<groupId>codes.thischwa</groupId>
<artifactId>cloudflaredns</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
<name>CloudflareDNS-java</name>
<inceptionYear>2025</inceptionYear>
<packaging>jar</packaging>
@@ -21,24 +21,26 @@
<project.reporting.outputEncoding>${file.encoding}</project.reporting.outputEncoding>
<!-- checkstyle -->
<checkstyle.version>10.21.3</checkstyle.version>
<checkstyle.version>12.3.0</checkstyle.version>
<checkstyle.plugin.version>3.6.0</checkstyle.plugin.version>
<checkstyle.config.location>${project.basedir}/src/checkstyle/google_custom_checks.xml
</checkstyle.config.location>
<checkstyle.includeTestResources>false</checkstyle.includeTestResources>
<checkstyle.violationSeverity>warning</checkstyle.violationSeverity>
<checkstyle.failOnViolation>false</checkstyle.failOnViolation>
<checkstyle.propertyExpansion>config_loc=${basedir}
</checkstyle.propertyExpansion>
<checkstyle.consoleOutput>true</checkstyle.consoleOutput>
<linkX-Ref>false</linkX-Ref>
<linkXRef>false</linkXRef>
<!-- 3rd party dependencies -->
<jackson.version>2.18.2</jackson.version>
<httpclient5.version>5.4.3</httpclient5.version>
<jackson.version>2.19.1</jackson.version>
<httpclient5.version>5.5.1</httpclient5.version>
<lombok.version>1.18.36</lombok.version>
<slf4j.version>2.0.17</slf4j.version>
<logback-classic.version>1.5.18</logback-classic.version>
<junit5.version>5.12.2</junit5.version>
<mockito-junit5.version>5.17.0</mockito-junit5.version>
<junit5.version>5.14.2</junit5.version>
<mockito-junit5.version>5.21.0</mockito-junit5.version>
<!-- sonarqube -->
<sonar.organization>th-schwarz</sonar.organization>
@@ -56,7 +58,7 @@
<developerConnection>scm:git:git@gitlab.com:th-schwarz/CloudflareDNS-java.git</developerConnection>
<connection>scm:git:git@gitlab.com:th-schwarz/CloudflareDNS-java.git</connection>
<url>https://gitlab.com/th-schwarz/CloudflareDNS-java</url>
<tag>v0.2.0</tag>
<tag>v0.3.0</tag>
</scm>
<distributionManagement>
@@ -149,7 +151,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<version>3.11.2</version>
<configuration>
<failOnError>false</failOnError>
<locale>en</locale>
@@ -168,7 +170,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.1.1</version>
<version>3.3.1</version>
<configuration>
<tagNameFormat>v@{project.version}</tagNameFormat>
<arguments>-DskipTests</arguments>
@@ -202,7 +204,7 @@
<!-- generates the code coverage report for sonar cube -->
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<version>0.8.13</version>
<executions>
<execution>
<id>prepare-agent</id>
@@ -261,6 +263,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<executions>
<execution>
<id>attach-delomboked-sources</id>
@@ -19,6 +19,7 @@ import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.jetbrains.annotations.NotNull;
/**
* Abstract base class for creating HTTP clients to interact with the Cloudflare API. Provides
@@ -27,23 +28,20 @@ import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
*/
@Slf4j
abstract class CfBasicHttpClient {
private final String baseUrl;
private final String authEmail;
private final String authKey;
private final String baseUrl;
private final CfDnsClientBuilder.CfAuth auth;
private final ObjectMapper objectMapper;
CfBasicHttpClient(String baseUrl, String authEmail, String authKey)
throws IllegalArgumentException {
if (authEmail == null || authEmail.isBlank()) {
throw new IllegalArgumentException("Authentication email must not be null or blank!");
}
if (authKey == null || authKey.isBlank()) {
throw new IllegalArgumentException("Authentication key must not be null or blank!");
}
/**
* Creates a new Cloudflare HTTP client with the specified base URL and authentication.
*
* @param baseUrl the base URL for the Cloudflare API
* @param auth the authentication mechanism to use
*/
CfBasicHttpClient(@NotNull String baseUrl, @NotNull CfDnsClientBuilder.CfAuth auth) {
this.baseUrl = baseUrl;
this.authEmail = authEmail;
this.authKey = authKey;
this.auth = auth;
this.objectMapper = JsonConf.initObjectMapper();
}
@@ -53,8 +51,9 @@ abstract class CfBasicHttpClient {
request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
request.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());
request.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
request.addHeader("X-Auth-Email", authEmail);
request.addHeader("X-Auth-Key", authKey);
if (request instanceof ClassicHttpRequest classicRequest) {
auth.applyAuth(classicRequest);
}
}).build();
}
@@ -15,8 +15,10 @@ import codes.thischwa.cf.model.ZoneMultipleResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
@@ -26,74 +28,59 @@ import org.jetbrains.annotations.Nullable;
* records and zones within the Cloudflare system, including creating, updating, retrieving, and
* deleting DNS records.
*
* <p>Example:
* <p>Example with API token authentication (recommended):
* <pre><code>
* // Create a new CfDnsClient instance
* CfDnsClient cfDnsClient = new CfDnsClient(
* "email@example.com",
* "yourApiKey"
* );
* // Create a new CfDnsClient instance with API token
* CfDnsClient cfDnsClient = new CfDnsClientBuilder()
* .withApiTokenAuth("your-api-token")
* .build();
*
* // Retrieve a zone
* ZoneEntity zone = cfDnsClient.zoneGet("example.com");
* System.out.println("Zone ID: " + zone.getId());
*
* // Retrieve records of a subdomain
* List&lt;{@link RecordEntity}&gt; records = cfDnsClient.recordGet(zone, "sld");
* List&lt;RecordEntity&gt; records = cfDnsClient.recordList(zone, "sld");
* records.forEach(record ->
* System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
* );
*
* // Create a record for the subdomain "api"
* RecordEntity created = cfDnsClient.recordCreateSld(zone, "api", 60, RecordType.A, "192.168.1.10");
* System.out.println("Created Record ID: " + created.getId());
* </code></pre>
*
* <p>Example with email/key authentication (legacy):
* <pre><code>
* CfDnsClient cfDnsClient = new CfDnsClientBuilder()
* .withEmailKeyAuth("email@example.com", "your-api-key")
* .build();
* </code></pre>
*
* <p>Example with exception throwing enabled:
* <pre><code>
* // Throws exception when results are empty
* CfDnsClient cfDnsClient = new CfDnsClientBuilder()
* .withApiTokenAuth("your-api-token")
* .withEmptyResultThrowsException(true)
* .build();
* </code></pre>
*
* <p>Example with custom base URL:
* <pre><code>
* CfDnsClient cfDnsClient = new CfDnsClientBuilder()
* .withApiTokenAuth("your-api-token")
* .withBaseUrl("https://custom-api.example.com")
* .build();
* </code></pre>
*/
@Slf4j
public class CfDnsClient extends CfBasicHttpClient {
private static final String DEFAULT_BASEURL = "https://api.cloudflare.com/client/v4";
private final ResponseValidator responseValidator;
private final boolean emptyResultThrowsException;
/**
* Constructs a new instance of {@code CfDnsClient}.
*
* @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 authentication
* process.
*/
public CfDnsClient(String authEmail, String authKey) {
this(DEFAULT_BASEURL, authEmail, authKey);
}
/**
* Constructs a new instance of {@code CfDnsClient}.
*
* @param baseUrl The base URL of the Cloudflare API to be used for requests.
* @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 authentication
* process.
*/
public CfDnsClient(String baseUrl, String authEmail, String 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. 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
* authentication process.
*/
public CfDnsClient(boolean emptyResultThrowsException, String authEmail, String authKey) {
this(emptyResultThrowsException, DEFAULT_BASEURL, authEmail, authKey);
}
/**
* Constructs a new instance of {@code CfDnsClient}.
*
@@ -101,14 +88,10 @@ public class CfDnsClient extends CfBasicHttpClient {
* 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.
* @param authKey The API key for authenticating the client with Cloudflare
* services.
* @param auth The authentication mechanism to use (ApiTokenAuth or EmailKeyAuth)
*/
public CfDnsClient(boolean emptyResultThrowsException, String baseUrl, String authEmail,
String authKey) {
super(baseUrl, authEmail, authKey);
CfDnsClient(boolean emptyResultThrowsException, String baseUrl, CfDnsClientBuilder.CfAuth auth) {
super(baseUrl, auth);
this.responseValidator = new ResponseValidator(emptyResultThrowsException);
this.emptyResultThrowsException = emptyResultThrowsException;
}
@@ -316,9 +299,9 @@ public class CfDnsClient extends CfBasicHttpClient {
RecordSingleResponse resp = postRequest(endpoint, rec, RecordSingleResponse.class);
checkResponse(resp);
log.info("Record {} of type {} successful created.", rec.getSld(), rec.getType());
RecordEntity record = resp.getResult();
record.setZoneId(zone.getId());
return record;
RecordEntity retRec = resp.getResult();
retRec.setZoneId(zone.getId());
return retRec;
}
/**
@@ -452,7 +435,10 @@ public class CfDnsClient extends CfBasicHttpClient {
throws CloudflareNotFoundException {
List<RecordEntity> filtered;
if (types != null && types.length > 0) {
filtered = recs.stream().filter(rec -> Arrays.asList(types).contains(RecordType.valueOf(rec.getType()))).collect(Collectors.toList());
Set<RecordType> allowedTypes = new HashSet<>(Arrays.asList(types));
filtered = recs.stream()
.filter(rec -> allowedTypes.contains(RecordType.valueOf(rec.getType())))
.collect(Collectors.toList());;
} else {
filtered = new ArrayList<>(recs);
}
@@ -506,4 +492,5 @@ public class CfDnsClient extends CfBasicHttpClient {
throws CloudflareApiException {
responseValidator.validate(resp, singleResultExpected);
}
}
@@ -0,0 +1,190 @@
package codes.thischwa.cf;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Builder class for configuring and creating instances of {@link CfDnsClient}.
* This class provides a fluent API for customizing the client settings,
* such as the base URL and authentication mechanism.
*/
public class CfDnsClientBuilder {
/**
* The default base URL for the Cloudflare v4 API requests made by the {@code CfDnsClient}.
*/
public static final String DEFAULT_BASEURL = "https://api.cloudflare.com/client/v4";
private boolean emptyResultThrowsException;
private CfAuth auth;
@Nullable
private String baseUrl;
/**
* Constructs a new instance of `CfDnsClientBuilder`.
*
* <p>This class serves as a builder for creating and configuring instances of a CfDnsClient. It provides
* a fluent API to set various optional configurations, such as API authentication methods and base
* URL, before constructing the client.
*
* <p>By using this constructor, you can initiate the building process with default settings, which can
* later be overridden using the provided builder methods.
*/
public CfDnsClientBuilder() {
}
/**
* Configures whether an exception should be thrown when an empty result is encountered
* during operations performed by the `CfDnsClient`.
*
* @param emptyResultThrowsException a boolean flag indicating if an exception should be thrown
* when an empty result is returned. If set to `true`, operations
* that result in an empty response will throw an exception;
* otherwise, they will not.
* @return the current instance of {@code CfDnsClientBuilder}, allowing for method chaining
* to further configure the builder.
*/
public CfDnsClientBuilder withEmptyResultThrowsException(boolean emptyResultThrowsException) {
this.emptyResultThrowsException = emptyResultThrowsException;
return this;
}
/**
* Sets the base URL to be used by the {@code CfDnsClient}.
* This method allows configuring the base URL for API requests, overriding any default value.
*
* @param baseUrl the base URL to be used for API requests
* @return the current instance of {@code CfDnsClientBuilder}, enabling method chaining
*/
public CfDnsClientBuilder withBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
/**
* Configures the authentication method for the {@code CfDnsClient} to use an API token.
* This is the recommended way to authenticate with the Cloudflare API, as it provides
* enhanced security and ease of use compared to other authentication mechanisms.
*
* @param apiToken the Cloudflare API token. This token is required for authenticating
* API requests and must not be null or blank.
* @return the current instance of {@code CfDnsClientBuilder}, allowing for method chaining
* to further configure the builder.
* @throws IllegalArgumentException if the {@code apiToken} is null or blank.
*/
public CfDnsClientBuilder withApiTokenAuth(String apiToken) {
this.auth = new ApiTokenAuth(apiToken);
return this;
}
/**
* Configures the authentication method for the {@code CfDnsClient} to use an email and API key.
* This approach uses the legacy authentication mechanism provided by Cloudflare, where requests
* are authenticated with a combination of an account's email address and API key.
*
* @param authEmail the email address associated with the Cloudflare account. This must not be null or blank.
* @param authKey the API key of the Cloudflare account. This must not be null or blank.
* @return the current instance of {@code CfDnsClientBuilder}, allowing for method chaining
* to further configure the builder.
* @throws IllegalArgumentException if {@code authEmail} or {@code authKey} is null or blank.
*/
public CfDnsClientBuilder withEmailKeyAuth(String authEmail, String authKey) {
this.auth = new EmailKeyAuth(authEmail, authKey);
return this;
}
/**
* Builds and returns a configured instance of {@code CfDnsClient}.
*
* <p>The method constructs a new {@code CfDnsClient} object based on the
* options set in the {@code CfDnsClientBuilder}. If no base URL has been
* explicitly configured, a default base URL will be used.
*
* @return a new instance of {@code CfDnsClient} configured with the
* specified options such as base URL, authentication details,
* and the exception-handling policy for empty results.
*/
public CfDnsClient build() {
String url = baseUrl == null ? DEFAULT_BASEURL : baseUrl;
return new CfDnsClient(emptyResultThrowsException, url, auth);
}
/**
* Interface for Cloudflare authentication mechanisms.
* Implementations of this interface provide different methods of authentication
* with the Cloudflare API (e.g., API token, email/key combination).
*/
interface CfAuth {
/**
* Applies authentication headers to the given HTTP request.
*
* @param request the HTTP request to authenticate
*/
void applyAuth(ClassicHttpRequest request);
}
/**
* Authentication mechanism using Cloudflare API token.
* This is the recommended authentication method for the Cloudflare API.
*/
static class ApiTokenAuth implements CfAuth {
private final String apiToken;
/**
* Creates a new API token authentication object.
*
* @param apiToken the Cloudflare API token
* @throws IllegalArgumentException if the API token is null or blank
*/
ApiTokenAuth(@NotNull String apiToken) {
if (apiToken.isBlank()) {
throw new IllegalArgumentException("API token must not be null or blank!");
}
this.apiToken = apiToken;
}
@Override
public void applyAuth(ClassicHttpRequest request) {
request.addHeader("Authorization", "Bearer " + apiToken);
}
}
/**
* Authentication mechanism using Cloudflare account email and API key.
* This is the legacy authentication method for the Cloudflare API.
*/
static class EmailKeyAuth implements CfAuth {
private final String authEmail;
private final String authKey;
/**
* Creates a new email/key authentication object.
*
* @param authEmail the email address associated with the Cloudflare account
* @param authKey the API key of the Cloudflare account
* @throws IllegalArgumentException if email or key is null or blank
*/
EmailKeyAuth(@NotNull String authEmail, @NotNull String authKey) {
if (authEmail.isBlank()) {
throw new IllegalArgumentException("Authentication email must not be null or blank!");
}
if (authKey.isBlank()) {
throw new IllegalArgumentException("Authentication key must not be null or blank!");
}
this.authEmail = authEmail;
this.authKey = authKey;
}
@Override
public void applyAuth(ClassicHttpRequest request) {
request.addHeader("X-Auth-Email", authEmail);
request.addHeader("X-Auth-Key", authKey);
}
}
}
@@ -46,18 +46,28 @@ class ResponseValidator {
private void validateResultCount(AbstractResponse resp, boolean singleResultExpected)
throws CloudflareApiException {
if (resp instanceof RecordMultipleResponse respMulti) {
if (singleResultExpected && respMulti.getResultInfo().totalCount() > 1) {
throw new CloudflareApiException(
"Unexpected result count: " + respMulti.getResultInfo().totalCount());
}
if (emptyResultThrowsException && respMulti.getResultInfo().totalCount() == 0) {
throw new CloudflareNotFoundException("No result found");
}
validateMultipleResponse(respMulti, singleResultExpected);
} else if (resp instanceof AbstractSingleResponse<?> respSingle) {
if (emptyResultThrowsException && respSingle.getResult() == null) {
validateSingleResponse(respSingle);
}
}
private void validateMultipleResponse(RecordMultipleResponse response, boolean singleResultExpected)
throws CloudflareApiException {
int totalCount = response.getResultInfo().totalCount();
if (singleResultExpected && totalCount > 1) {
throw new CloudflareApiException("Unexpected result count: " + totalCount);
}
if (emptyResultThrowsException && totalCount == 0) {
throw new CloudflareNotFoundException("No result found");
}
}
private void validateSingleResponse(AbstractSingleResponse<?> response)
throws CloudflareNotFoundException {
if (emptyResultThrowsException && response.getResult() == null) {
throw new CloudflareNotFoundException("No result found");
}
}
}
@@ -45,16 +45,16 @@ public class RecordOperationsImpl implements RecordOperations {
@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);
List<RecordEntity> recs = get();
if (recs.isEmpty()) {
throw new CloudflareApiException("No recs found to update for subdomain: " + sld);
}
if (records.size() > 1) {
throw new CloudflareApiException("Multiple records found. Please use recordUpdate() directly for precise control.");
if (recs.size() > 1) {
throw new CloudflareApiException("Multiple recs found. Please use recordUpdate() directly for precise control.");
}
RecordEntity record = records.get(0);
record.setContent(newContent);
return client.recordUpdate(zone, record);
RecordEntity rec = recs.get(0);
rec.setContent(newContent);
return client.recordUpdate(zone, rec);
}
@Override
@@ -64,10 +64,10 @@ public class RecordEntity extends AbstractEntity {
*/
public static RecordEntity build(String name, RecordType type, Integer ttl, String content) {
RecordEntity rec = new RecordEntity();
rec.setName(name);
rec.setType(type.getType());
rec.setTtl(ttl);
rec.setContent(content);
rec.name = name;
rec.type = type.getType();
rec.ttl = ttl;
rec.content = content;
return rec;
}
@@ -81,7 +81,7 @@ public class RecordEntity extends AbstractEntity {
public static RecordEntity build(String id, String content) {
RecordEntity rec = new RecordEntity();
rec.setId(id);
rec.setContent(content);
rec.content = content;
return rec;
}
@@ -104,8 +104,12 @@ public class RecordEntity extends AbstractEntity {
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);
RecordEntity rec = new RecordEntity();
rec.setId(id);
rec.name = name;
rec.type = recordType.getType();
rec.ttl = ttl;
rec.content = content;
return rec;
}
@@ -199,6 +199,6 @@ public enum RecordType {
@Override
public String toString() {
return getType();
return type;
}
}
@@ -6,15 +6,12 @@ package codes.thischwa.cf.model;
* <p>This class contains information about the current page, page size, total pages, and result
* counts, which can be utilized in managing and navigating through paginated data.
*
* <ul>
* <li><b>page:</b> The current page number.
* <li><b>perPage:</b> The number of results per page.
* <li><b>totalPages:</b> The total number of pages available.
* <li><b>count:</b> The number of results on the current page.
* <li><b>totalCount:</b> The total number of results across all pages.
* </ul>
* @param page The current page number.
* @param perPage The number of results per page.
* @param totalPages The total number of pages available.
* @param count The number of results on the current page.
* @param totalCount The total number of results across all pages.
*/
public record ResultInfo(int page, int perPage, int totalPages, int count, int totalCount) {
/**
@@ -22,24 +22,22 @@ public class CfClientPenTest {
private static final String ZONE_STR = "mein-d-ns.de"; // existing baseline zone
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_TOKEN = System.getenv("API_TOKEN");
@BeforeAll
static void checkEnv() {
assumeTrue(API_EMAIL != null && !API_EMAIL.isBlank(), "API_EMAIL not set; skipping pen tests");
assumeTrue(API_KEY != null && !API_KEY.isBlank(), "API_KEY not set; skipping pen tests");
assumeTrue(API_TOKEN != null && !API_TOKEN.isBlank(), "API_TOKEN not set; skipping pen tests");
}
private CfDnsClient newClient() {
return new CfDnsClient(true, API_EMAIL, API_KEY);
return new CfDnsClientBuilder().withEmptyResultThrowsException(true).withApiTokenAuth(API_TOKEN).build();
}
@Test
@DisplayName("Invalid credentials should not authenticate and must throw CloudflareApiException")
void testInvalidCredentialsShouldFail() {
// Use syntactically valid but wrong credentials
CfDnsClient badClient = new CfDnsClient("invalid@example.com", UUID.randomUUID().toString());
CfDnsClient badClient = new CfDnsClientBuilder().withEmailKeyAuth("invalid@example.com", UUID.randomUUID().toString()).build();
assertThrows(CloudflareApiException.class, badClient::zoneList);
}
@@ -28,15 +28,13 @@ public class CfClientTest {
private static final String SLD_STR = "devsld";
private static final int TTL = 60;
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_TOKEN = System.getenv("API_TOKEN");
private final CfDnsClient client = new CfDnsClient(true, API_EMAIL, API_KEY);
private final CfDnsClient client = new CfDnsClientBuilder().withEmptyResultThrowsException(true).withApiTokenAuth(API_TOKEN).build();
@BeforeAll
static void checkEnv() {
assumeTrue(API_EMAIL != null && !API_EMAIL.isBlank(), "API_EMAIL not set; skipping pen tests");
assumeTrue(API_KEY != null && !API_KEY.isBlank(), "API_KEY not set; skipping pen tests");
assumeTrue(API_TOKEN != null && !API_TOKEN.isBlank(), "API_TOKEN not set; skipping client tests");
}
@Test
@@ -106,13 +104,13 @@ public class CfClientTest {
void testDns() throws Exception {
// starting point: already existing zone 'mein-d-ns.de'
ZoneEntity z = client.zoneGet(ZONE_STR);
assertEquals("0a83dd6e7f8c46039f2517bbded8115e", z.getId());
assertEquals("cf9d8b12f61423f280e0a3ea2a96d921", z.getId());
assertEquals("mein-d-ns.de", z.getName());
assertEquals("active", z.getStatus());
assertEquals(2, z.getNameServers().size());
assertTrue(z.getNameServers().contains("sergi.ns.cloudflare.com"));
assertEquals(4, z.getOriginalNameServers().size());
assertTrue(z.getOriginalNameServers().contains("a.ns14.net"));
assertTrue(z.getNameServers().contains("rafe.ns.cloudflare.com"));
assertTrue(z.getOriginalNameServers().size() >= 2);
assertTrue(z.getOriginalNameServers().contains("blair.ns.cloudflare.com"));
assertNotNull(z.getActivatedOn());
assertNotNull(z.getModifiedOn());
assertNotNull(z.getCreatedOn());
@@ -224,14 +222,6 @@ public class CfClientTest {
}
}
@Test
void testException() {
assertThrows(IllegalArgumentException.class, () -> new CfDnsClient(null, "key"));
assertThrows(IllegalArgumentException.class, () -> new CfDnsClient("email", null));
assertThrows(IllegalArgumentException.class, () -> new CfDnsClient("email", ""));
assertThrows(IllegalArgumentException.class, () -> new CfDnsClient("", "key"));
}
@Test
void testRecordEntityInvalidType() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,