All Downloads are FREE. Search and download functionalities are using the official Maven repository.

api_assured.Caller Maven / Gradle / Ivy

There is a newer version: 1.6.9
Show newest version
package api_assured;

import api_assured.exceptions.FailedCallException;
import com.fasterxml.jackson.core.JsonProcessingException;
import okio.Buffer;
import properties.PropertyUtilities;
import retrofit2.Call;
import retrofit2.Response;
import utils.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

import static utils.MappingUtilities.Json.*;
import static utils.reflection.ReflectionUtilities.getPreviousMethodName;
import static utils.StringUtilities.Color.*;

/**
 * This abstract class represents a caller that performs API calls, logs the results and returns objects in response bodies.
 * It contains methods for performing and getting responses from API calls, as well as a method for printing the response body.
 * It also has a static boolean variable for keeping logs, an ObjectMapper for JSON serialization and deserialization,
 * a StringUtilities object for string manipulation, and a Printer object for logging.
 *
 * @author Umut Ay Bora
 * @version 1.4.0 (Documented in 1.4.0, released in an earlier version)
 */
@SuppressWarnings("unused")
public abstract class Caller {

    /**
     * A static boolean variable that determines whether logs should be kept for API calls.
     */
    protected static boolean keepLogs;

    /**
     * A Printer object for logging.
     */
    private static final Printer log = new Printer(Caller.class);

    /**
     * Constructs a Caller object and initializes the ObjectMapper object and the keepLogs variable.
     */
    public Caller(){
        keepLogs = Boolean.parseBoolean(PropertyUtilities.getProperty("keep-api-logs", "true"));
    }

    /**
     * Performs the given call and processes the response. This method provides advanced error handling capabilities.
     *
     * @param call The call to be executed. This is a retrofit2.Call object, which represents a request that has been prepared for execution.
     * @param strict If true, throws a FailedCallException when the call fails or the response is not successful. If false, returns null in these cases.
     * @param printBody If true, prints the body of the response. This may be useful for debugging purposes.
     * @param errorModels Varargs parameter. Each ErrorModel class is used to try to parse the error response if the call was not successful.
     *
     * @return A ResponseType object. If the call was successful, this is the body of the response. If the call was not successful and strict is false, this may be a parsed error response, or null if parsing the error response failed.
     *
     * @throws FailedCallException If strict is true, and the call failed or the response was not successful.
     *
     * @param  The type of the successful response body.
     * @param  The type of the return value in this method. This is either SuccessModel or ErrorModel.
     */
    @SuppressWarnings("unchecked")
    protected static  ReturnType perform(
            Call call,
            Boolean strict,
            Boolean printBody,
            Class... errorModels){
        Response response = call(call, strict, printBody, getPreviousMethodName());
        return response.isSuccessful() ? (ReturnType) response.body() : getErrorBody(response, errorModels);
    }

    /**
     * Gets the response from an API call and logs the results.
     *
     * @param call the Call object representing the API call
     * @param strict a boolean indicating whether the call should be strict (i.e. throw an exception if the response is not successful)
     * @param printBody a boolean indicating whether the response body should be printed
     * @return the Response object representing the API response
     * @throws FailedCallException if the call is strict and the response is not successful
     */
    protected static  Response getResponse(Call call, Boolean strict, Boolean printBody){
        return call(call, strict, printBody, getPreviousMethodName());
    }

    /**
     * Gets the response from an API call and logs the results.
     *
     * @param call the Call object representing the API call
     * @param strict a boolean indicating whether the call should be strict (i.e. throw an exception if the response is not successful)
     * @param printBody a boolean indicating whether the response body should be printed
     * @return the Response object representing the API response
     * @throws FailedCallException if the call is strict and the response is not successful
     */
    protected static  Response getResponse(String serviceName, Call call, Boolean strict, Boolean printBody){
        return call(call, strict, printBody, serviceName);
    }

    /**
     * Executes the given call and returns a Pair containing the response and potential error model.
     * This method provides advanced error handling capabilities.
     *
     * @param call The call to be executed. This is a retrofit2.Call object, which represents a request that has been prepared for execution.
     * @param strict If true, throws a FailedCallException when the call fails or the response is not successful. If false, returns null in these cases.
     * @param printBody If true, prints the body of the response. This may be useful for debugging purposes.
     * @param errorModels Varargs parameter. Each ErrorModel class is used to try to parse the error response if the call was not successful.
     *
     * @return A Pair object with the SuccessModel response as the first element and ErrorModel as the second element. If the call was successful, the second element is null.
     * If the call was not successful and strict is false, the second element may be a parsed error response, or null if parsing the error response failed.
     *
     * @throws FailedCallException If strict is true and the call failed or the response was not successful.
     *
     * @param  The type of the successful response body.
     * @param  The type of the error response body.
     */
    protected static  ResponsePair, ErrorModel> getResponse(
            Call call,
            Boolean strict,
            Boolean printBody,
            Class... errorModels
    ){
        Response response = call(call, strict, printBody, getPreviousMethodName());
        return response.isSuccessful() ?
                new ResponsePair<>(response, null) :
                new ResponsePair<>(response, getErrorBody(response, errorModels));
    }

    /**
     * Clones and logs the given response.
     *
     * @param          The type of the response body.
     * @param call        The original call to execute and log.
     * @param printBody   Flag to indicate whether the response body should be logged.
     * @return            A new cloned response object.
     */
    private static  Response getResponse(Call call, boolean printBody) throws IOException {
        Response response = call.execute();
        if (response.isSuccessful()) {
            T body = response.body();
            if (keepLogs) log.success("The response code is: " + response.code());
            if (keepLogs && response.message().length()>0) log.info(response.message());
            if (printBody) log.info("The response body is: \n" + getJsonString(body));
            return Response.success(body, response.raw());
        }
        else {
            log.warning("The response code is: " + response.code());
            if (response.message().length()>0) log.warning(response.message());
            if (response.errorBody() != null && printBody) {
                Object errorBody = getJsonString(getErrorObject(response, Object.class));
                String errorLog = errorBody.equals("null") ? "The error body is empty." : "The error body is: \n" + errorBody;
                log.warning(errorLog);
            }
            return Response.error(response.errorBody(), response.raw());
        }
    }

    /**
     * Extracts the error object from the given response and attempts to deserialize it into the specified model.
     * 

* The method tries to read the error content from the response and then deserialize it into a generic model type. * If the deserialization fails or other issues occur while processing the error content, a runtime exception is thrown. *

* * @param The generic type representing the desired structure of the error object. * @param response The response containing the potential error data. * @return A deserialized error object instance of type {@code Model}. * @throws RuntimeException if there's an issue processing the error content or deserializing it. * * @see MappingUtilities.Json#fromJsonString(String, Class) */ private static ErrorModel getErrorObject(Response response, Class errorModel) throws JsonProcessingException { assert response.errorBody() != null; try (Buffer errorBuffer = response.errorBody().source().getBuffer().clone()) { String bodyString = errorBuffer.readString(StandardCharsets.UTF_8); if (!StringUtilities.isBlank(bodyString)) return fromJsonString(bodyString, errorModel); else return null; } } /** * Logs the HTTP method, service name, and URL for a given call if logging is enabled. * * @param The type of the response body. * @param call The call object containing request details. * @param serviceName The name of the service being called. */ private static void printCallSpecifications(Call call, String serviceName){ if (keepLogs) log.info("Performing " + StringUtilities.markup(PALE, call.request().method()) + " call for '" + StringUtilities.markup(PALE, serviceName) + "' service on url: " + call.request().url() ); } /** * Executes the given call, logs the response or error, and optionally throws an exception for non-successful responses. * * @param call The call object to execute. * @param strict Flag to indicate whether an exception should be thrown for non-successful responses. * @param printBody Flag to indicate whether the response body should be logged. * @param serviceName The name of the service being called. * @return The response object from the executed call. * @throws RuntimeException If there's an I/O error during call execution. * @throws FailedCallException If the call is strict and the response is not successful. */ private static Response call( Call call, boolean strict, boolean printBody, String serviceName){ try { printCallSpecifications(call, serviceName); Response response = getResponse(call, printBody); if (strict && !Objects.requireNonNull(response).isSuccessful()) throw new FailedCallException( "The strict call performed for " + serviceName + " service returned response code " + response.code() ); else return response; } catch (IOException e) {throw new RuntimeException(e);} } /** * Attempts to extract and deserialize the error model from the given response using the specified error models. *

* The method iterates through the provided error models, trying to deserialize the error content of the response * to each one until a successful deserialization is found or all error models have been tested. * If none of the error models match, a runtime exception is thrown. *

* * @param The generic type representing the expected error model structure. * @param response The response containing the potential error data. * @param errorModels Varargs array of error model classes to attempt deserialization. * @return A deserialized error model instance of type {@code ErrorModel} if a match is found. * @throws RuntimeException if none of the provided error models match the error content of the response. * * @see MappingUtilities.Json#fromJsonString(String, Class) * @see MappingUtilities.Json#getJsonStringFor(Object) * @see #getErrorObject(Response, Class) */ @SuppressWarnings("unchecked") private static ErrorModel getErrorBody(Response response, Class... errorModels){ for (Class errorModel:errorModels){ try { return (ErrorModel) fromJsonString(getJsonStringFor(getErrorObject(response, errorModel)), errorModel); } catch (JsonProcessingException ignored) {} } throw new RuntimeException("Error model(s) did not match the error body!"); } /** * Returns whether logs are being kept for API calls. * * @return a boolean indicating whether logs are being kept for API calls */ public static boolean keepsLogs() { return keepLogs; } /** * Sets whether logs should be kept for API calls. * * @param keepLogs a boolean indicating whether logs should be kept for API calls */ public static void keepLogs(boolean keepLogs) { Caller.keepLogs = keepLogs; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy