FluentApiClient.java
package com.guinetik.rr.api;
import com.guinetik.rr.RocketRestConfig;
import com.guinetik.rr.http.FluentHttpClient;
import com.guinetik.rr.http.RocketClientFactory;
import com.guinetik.rr.request.RequestBuilder;
import com.guinetik.rr.request.RequestSpec;
import com.guinetik.rr.result.ApiError;
import com.guinetik.rr.result.Result;
import java.util.Map;
/**
* A fluent API client that uses the {@link Result} pattern instead of exceptions.
*
* <p>This client provides a functional, declarative approach to API calls where errors
* are returned as values rather than thrown as exceptions. This makes error handling
* explicit and composable.
*
* <h2>Basic Usage</h2>
* <pre class="language-java"><code>
* FluentApiClient client = new FluentApiClient("https://api.example.com", config);
*
* Result<User, ApiError> result = client.get("/users/1", User.class);
*
* // Handle success and failure explicitly
* result.match(
* user -> System.out.println("Found: " + user.getName()),
* error -> System.err.println("Error " + error.getStatusCode() + ": " + error.getMessage())
* );
* </code></pre>
*
* <h2>Chaining Operations</h2>
* <pre class="language-java"><code>
* String userName = client.get("/users/1", User.class)
* .map(User::getName)
* .map(String::toUpperCase)
* .getOrElse("Unknown");
* </code></pre>
*
* <h2>POST with Body</h2>
* <pre class="language-java"><code>
* CreateUserRequest request = new CreateUserRequest("John", "john@example.com");
*
* Result<User, ApiError> result = client.post("/users", request, User.class);
*
* if (result.isSuccess()) {
* User created = result.getValue();
* System.out.println("Created user with ID: " + created.getId());
* }
* </code></pre>
*
* @author guinetik <guinetik@gmail.com>
* @see Result
* @see ApiError
* @see AbstractApiClient
* @since 1.0.0
*/
public class FluentApiClient extends AbstractApiClient {
private final FluentHttpClient fluentClient;
/**
* Creates a new FluentApiClient with the specified base URL and configuration.
*
* @param baseUrl The base URL for API requests
* @param config The RocketRest configuration
*/
public FluentApiClient(String baseUrl, RocketRestConfig config) {
super(baseUrl, config, createFluentHttpClient(baseUrl, config));
this.fluentClient = (FluentHttpClient) httpClient;
}
/**
* Creates a new FluentApiClient with the specified base URL, configuration, and a
* pre-configured FluentHttpClient instance.
*
* @param baseUrl The base URL for API requests
* @param config The RocketRest configuration
* @param fluentClient A pre-configured FluentHttpClient instance
*/
public FluentApiClient(String baseUrl, RocketRestConfig config, FluentHttpClient fluentClient) {
super(baseUrl, config, fluentClient);
this.fluentClient = fluentClient;
}
/**
* Executes a request and returns a Result object instead of throwing exceptions.
*
* @param <Req> The type of the request
* @param <Res> The type of the response
* @param requestSpec The request specification
* @return A Result containing either the response or an ApiError
*/
public <Req, Res> Result<Res, ApiError> executeWithResult(RequestSpec<Req, Res> requestSpec) {
// Apply any request transformations from the abstract client
refreshToken(); // Refresh token if needed
logRequest(requestSpec); // Log the request if enabled
// Delegate to the fluent client
return fluentClient.executeWithResult(requestSpec);
}
/**
* Performs a GET request using the Result pattern.
*
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Res> Result<Res, ApiError> get(String endpoint, Class<Res> responseType) {
return this.<Res>get(endpoint, null, responseType);
}
/**
* Performs a GET request with headers using the Result pattern.
*
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param headers The request headers
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Res> Result<Res, ApiError> get(String endpoint, Map<String, String> headers, Class<Res> responseType) {
RequestSpec<Void, Res> request = RequestBuilder.<Void, Res>get(endpoint)
.headers(createHeaders(headers))
.responseType(responseType)
.build();
return this.<Void, Res>executeWithResult(request);
}
/**
* Performs a POST request using the Result pattern.
*
* @param <Req> The type of the request body
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param body The request body
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Req, Res> Result<Res, ApiError> post(String endpoint, Req body, Class<Res> responseType) {
return this.<Req, Res>post(endpoint, body, null, responseType);
}
/**
* Performs a POST request with headers using the Result pattern.
*
* @param <Req> The type of the request body
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param body The request body
* @param headers The request headers
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Req, Res> Result<Res, ApiError> post(String endpoint, Req body, Map<String, String> headers, Class<Res> responseType) {
RequestSpec<Req, Res> request = RequestBuilder.<Req, Res>post(endpoint)
.headers(createHeaders(headers))
.body(body)
.responseType(responseType)
.build();
return this.<Req, Res>executeWithResult(request);
}
/**
* Performs a PUT request using the Result pattern.
*
* @param <Req> The type of the request body
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param body The request body
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Req, Res> Result<Res, ApiError> put(String endpoint, Req body, Class<Res> responseType) {
return this.<Req, Res>put(endpoint, body, null, responseType);
}
/**
* Performs a PUT request with headers using the Result pattern.
*
* @param <Req> The type of the request body
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param body The request body
* @param headers The request headers
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Req, Res> Result<Res, ApiError> put(String endpoint, Req body, Map<String, String> headers, Class<Res> responseType) {
RequestSpec<Req, Res> request = RequestBuilder.<Req, Res>put(endpoint)
.headers(createHeaders(headers))
.body(body)
.responseType(responseType)
.build();
return this.<Req, Res>executeWithResult(request);
}
/**
* Performs a DELETE request using the Result pattern.
*
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Res> Result<Res, ApiError> delete(String endpoint, Class<Res> responseType) {
return this.<Res>delete(endpoint, null, responseType);
}
/**
* Performs a DELETE request with headers using the Result pattern.
*
* @param <Res> The type of the response
* @param endpoint The API endpoint
* @param headers The request headers
* @param responseType The class of the response type
* @return A Result containing either the response or an ApiError
*/
public <Res> Result<Res, ApiError> delete(String endpoint, Map<String, String> headers, Class<Res> responseType) {
RequestSpec<Void, Res> request = RequestBuilder.<Void, Res>delete(endpoint)
.headers(createHeaders(headers))
.responseType(responseType)
.build();
return this.<Void, Res>executeWithResult(request);
}
/**
* Shutdown the client and release resources.
*/
public void shutdown() {
// Currently just a placeholder for API compatibility with AsyncApiClient
// FluentHttpClient doesn't require explicit shutdown
}
/**
* Creates a FluentHttpClient with the appropriate options from config.
*
* @param baseUrl The base URL for API requests
* @param config The RocketRest configuration
* @return A new FluentHttpClient
*/
private static FluentHttpClient createFluentHttpClient(String baseUrl, RocketRestConfig config) {
return RocketClientFactory.fromConfig(config)
.buildFluent();
}
}