Skip to content

Commit

Permalink
Refactor: revise comments V1 (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobakavosne authored Nov 22, 2024
1 parent 4e35245 commit 56fa116
Show file tree
Hide file tree
Showing 18 changed files with 501 additions and 207 deletions.
29 changes: 25 additions & 4 deletions src/main/scala/api/ErrorHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,45 @@ import io.circe.generic.auto._
import org.http4s.circe.CirceEntityEncoder._

/**
* Represents an error response returned by the server.
* Represents a standard error response returned by the server in JSON format.
*
* This case class is used to encapsulate error details, making it easy to serialise into a consistent JSON structure
* for HTTP error responses.
*
* @param error
* The type of error (e.g., "NotFound", "BadRequest").
* A string identifying the type of error (e.g., "NotFound", "BadRequest").
* @param message
* A descriptive message about the error.
* A descriptive message providing additional details about the error.
*/
final case class ErrorResponse(error: String, message: String)

/**
* Provides error handling for HTTP routes in a standardised way.
*
* The `ErrorHandler` wraps existing HTTP routes and ensures that:
* - `NotFound` errors return a `404` status with a JSON-encoded `ErrorResponse`.
* - Validation errors (e.g., `IllegalArgumentException`) return a `400` status.
* - Unexpected errors return a `500` status with a generic message.
*
* This utility promotes consistent error handling across the application.
*/
object ErrorHandler {

/**
* Wraps the provided HTTP routes with error handling logic.
*
* This function ensures that errors raised during route processing are captured and translated into appropriate HTTP
* responses with JSON-encoded error messages.
*
* Error Handling:
* - `NoSuchElementException`: Translates to a `404 Not Found` response.
* - `IllegalArgumentException`: Translates to a `400 Bad Request` response.
* - Any other exception: Translates to a `500 Internal Server Error` response.
*
* @param routes
* The `HttpRoutes[IO]` to be wrapped with error handling.
* @return
* A new `HttpRoutes[IO]` that handles errors and returns appropriate HTTP responses with JSON error messages.
* A new `HttpRoutes[IO]` that handles errors consistently and returns meaningful responses.
*/
def apply(routes: HttpRoutes[IO]): HttpRoutes[IO] = HttpRoutes.of[IO] { request =>
routes(request).getOrElseF(
Expand Down
19 changes: 13 additions & 6 deletions src/main/scala/api/ServerBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import org.http4s.ember.server.EmberServerBuilder
import org.http4s.HttpRoutes

/**
* A class responsible for building and starting an HTTP server.
* A utility class for constructing and starting an HTTP server.
*
* This class uses the `EmberServerBuilder` to set up a server with the provided HTTP routes. It integrates error
* handling for routes and ensures the server is properly managed as a `Resource`, allowing safe startup and shutdown.
*
* @param routes
* The HTTP routes to be served by the server.
Expand All @@ -19,15 +22,19 @@ class ServerBuilder(
) {

/**
* Starts the HTTP server with the specified host and port configuration.
* Configures and starts an HTTP server with the specified host and port.
*
* This method uses the `EmberServerBuilder` to create and manage the server. It wraps the provided routes with the
* `ErrorHandler` to ensure consistent error handling, and builds an HTTP application that listens on the specified
* host and port.
*
* @param host
* The host address on which the server will listen.
* The host address on which the server will listen (e.g., `Host.fromString("127.0.0.1")`).
* @param port
* The port number on which the server will listen.
* The port number on which the server will listen (e.g., `Port.fromInt(8080)`).
* @return
* A `Resource[IO, Server]` that represents the running HTTP server. The server will be properly managed and
* terminated when the `Resource` is released.
* A `Resource[IO, Server]` that represents the running HTTP server. The server is automatically cleaned up when the
* `Resource` is released.
*/
def startServer(
host: Host,
Expand Down
24 changes: 19 additions & 5 deletions src/main/scala/api/endpoints/flow/ReaktoroEndpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,37 @@ object ComputePropsRequest {
/**
* Defines the HTTP routes for interacting with the ReaktoroService.
*
* This class sets up the routing for the API endpoints that allow clients to compute system properties for chemical
* reactions. It integrates with the `ReaktoroService` to perform the computations and handle requests.
*
* @param reaktoroService
* The service handling system property computations.
* The service handling system property computations. This should implement the core logic to process reaction
* properties using the provided inputs.
*/

class ReaktoroEndpoints(
reaktoroService: ReaktoroService[IO]
) {

/**
* HTTP POST route for computing system properties for a reaction.
* HTTP POST route for computing system properties for a chemical reaction.
*
* Endpoint: `/api/system/properties`
*
* @param req
* The HTTP request containing a `ComputePropsRequest` object in the body.
* This endpoint accepts a JSON payload containing a `ComputePropsRequest` object, which includes:
* - `reactionId`: The identifier of the reaction.
* - `database`: The database to use for the computation.
* - `amounts`: The list of molecule amounts relevant to the computation.
*
* The route invokes the `ReaktoroService` to compute the system properties for the given reaction and returns the
* results as a JSON response. If an error occurs during processing, an appropriate error response is sent.
*
* @return
* A JSON response containing the computed system properties or an error message.
* An HTTP response:
* - `200 OK`: With a JSON body containing the computed system properties.
* - `500 Internal Server Error`: If the service fails to process the request.
*/

private val computeSystemPropsForReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ POST -> Root / "system" / "properties" =>
req.as[ComputePropsRequest].flatMap {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,44 @@ import core.domain.preprocessor.{MechanismDetails, Reaction, ReactionDetails}
import core.errors.http.preprocessor.ReactionError

/**
* Defines the HTTP routes for handling reactions and mechanisms.
* Provides HTTP endpoints for managing reactions and mechanisms in the preprocessor module.
*
* This class provides endpoints for performing CRUD operations on reactions and retrieving mechanism details. Each
* endpoint calls the appropriate service, and returns a response in JSON format.
* This class defines routes for:
* - Fetching reaction and mechanism details by ID.
* - Creating new reactions.
* - Deleting existing reactions.
*
* All endpoints interact with the `ReactionService` and `MechanismService` for business logic and return appropriate
* JSON responses or error messages.
*
* @param reactionService
* The service responsible for handling reaction-related operations, such as fetching, creating, and deleting
* reactions.
* Handles CRUD operations related to reactions.
* @param mechanismService
* The service responsible for handling mechanism-related operations, such as fetching mechanism details.
* Handles retrieval of mechanism details.
*/
class PreprocessorEndpoints(
reactionService: ReactionService[IO],
mechanismService: MechanismService[IO]
) {

/**
* HTTP GET route for fetching a reaction by its ID.
* HTTP GET route for fetching reaction details by ID.
*
* Endpoint: `/api/reaction/{id}`
*
* This route validates the provided ID, fetches the reaction details from the `ReactionService`, and returns the
* details in JSON format. If the ID is invalid or the reaction is not found, it returns an appropriate error
* response.
* Validates the provided reaction ID, fetches the corresponding details using the `ReactionService`, and returns them
* in JSON format. Returns an appropriate error response if:
* - The ID is invalid.
* - The reaction is not found.
* - An unexpected error occurs during processing.
*
* @param id
* The string representation of the reaction ID to fetch.
* The string representation of the reaction ID.
* @return
* - `200 OK` with the `ReactionDetails` object in JSON format if the reaction is found.
* - `400 Bad Request` if the ID is invalid.
* - `404 Not Found` if no reaction with the given ID exists.
* - `500 Internal Server Error` if an unexpected error occurs.
* - `200 OK`: Contains the `ReactionDetails` in JSON format.
* - `400 Bad Request`: If the ID is invalid.
* - `404 Not Found`: If no reaction exists with the given ID.
* - `500 Internal Server Error`: For unexpected errors.
*/
private val getReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "reaction" / id =>
Expand All @@ -69,15 +75,15 @@ class PreprocessorEndpoints(
*
* Endpoint: `/api/reaction`
*
* This route reads a `Reaction` object from the request body, passes it to the `ReactionService` to create a new
* reaction, and returns the created reaction details in JSON format.
* Accepts a `Reaction` object in the request body and uses the `ReactionService` to create a new reaction. Returns
* the created reaction details in JSON format or an error response in case of failure.
*
* @param req
* The HTTP request containing the `Reaction` object in the body.
* The HTTP request containing the `Reaction` object.
* @return
* - `201 Created` with the created `Reaction` object in JSON format on success.
* - `400 Bad Request` if the request body is invalid or creation fails.
* - `500 Internal Server Error` if an unexpected error occurs.
* - `201 Created`: Contains the created `Reaction` in JSON format.
* - `400 Bad Request`: If the request body is invalid or creation fails.
* - `500 Internal Server Error`: For unexpected errors.
*/
private val postReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ POST -> Root / "reaction" =>
Expand All @@ -92,19 +98,19 @@ class PreprocessorEndpoints(
}

/**
* HTTP DELETE route for deleting a reaction by its ID.
* HTTP DELETE route for deleting a reaction by ID.
*
* Endpoint: `/api/reaction/{id}`
*
* This route validates the provided ID, deletes the reaction using the `ReactionService`, and returns an appropriate
* response. If the ID is invalid or the deletion fails, an error response is returned.
* Validates the provided reaction ID and attempts to delete the reaction using the `ReactionService`. Returns an
* appropriate response based on the success or failure of the operation.
*
* @param id
* The string representation of the reaction ID to delete.
* The string representation of the reaction ID.
* @return
* - `204 No Content` if the reaction is successfully deleted.
* - `400 Bad Request` if the ID is invalid or deletion fails.
* - `500 Internal Server Error` if an unexpected error occurs.
* - `204 No Content`: If the reaction is successfully deleted.
* - `400 Bad Request`: If the ID is invalid or deletion fails.
* - `500 Internal Server Error`: For unexpected errors.
*/
private val deleteReactionRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case DELETE -> Root / "reaction" / id =>
Expand All @@ -119,21 +125,23 @@ class PreprocessorEndpoints(
}

/**
* HTTP GET route for fetching a mechanism by its ID.
* HTTP GET route for fetching mechanism details by ID.
*
* Endpoint: `/api/mechanism/{id}`
*
* This route validates the provided ID, fetches the mechanism details from the `MechanismService`, and returns the
* details in JSON format. If the ID is invalid or the mechanism is not found, it returns an appropriate error
* response.
* Validates the provided mechanism ID, fetches the corresponding details using the `MechanismService`, and returns
* them in JSON format. Returns an appropriate error response if:
* - The ID is invalid.
* - The mechanism is not found.
* - An unexpected error occurs during processing.
*
* @param id
* The string representation of the mechanism ID to fetch.
* The string representation of the mechanism ID.
* @return
* - `200 OK` with the `MechanismDetails` object in JSON format if the mechanism is found.
* - `400 Bad Request` if the ID is invalid.
* - `404 Not Found` if no mechanism with the given ID exists.
* - `500 Internal Server Error` if an unexpected error occurs.
* - `200 OK`: Contains the `MechanismDetails` in JSON format.
* - `400 Bad Request`: If the ID is invalid.
* - `404 Not Found`: If no mechanism exists with the given ID.
* - `500 Internal Server Error`: For unexpected errors.
*/
private val getMechanismRoute: HttpRoutes[IO] = HttpRoutes.of[IO] {
case GET -> Root / "mechanism" / id =>
Expand All @@ -149,22 +157,8 @@ class PreprocessorEndpoints(
}
}

/**
* Validates the given string as an integer ID.
*
* @param id
* The string to validate.
* @return
* An `Option[Int]` containing the valid integer ID, or `None` if the validation fails.
*/
private def validateId(id: String): Option[Int] = id.toIntOption

/**
* Combines all defined HTTP routes and applies middleware for logging.
*
* @return
* A single `HttpRoutes[IO]` instance containing all endpoints.
*/
val routes: HttpRoutes[IO] = Logger.httpRoutes(logHeaders = false, logBody = true)(
Router(
"/api" -> (getReactionRoute <+> postReactionRoute <+> deleteReactionRoute <+> getMechanismRoute)
Expand Down
48 changes: 36 additions & 12 deletions src/main/scala/app/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,48 @@ import app.units.ServiceResources.{
}

/**
* Entry point for the application. Responsible for configuring and starting all resources and services.
* Main entry point for the application.
*
* This object configures and starts all required resources and services for the application, including:
* - Actor system setup for Akka-based concurrency.
* - HTTP client setup for external API interactions.
* - Distributed cache management with a configurable time-to-live (TTL).
* - Initialisation of core services (`MechanismService`, `ReactionService`, `ReaktoroService`).
* - HTTP server setup for serving API endpoints.
*
* Proper lifecycle management is ensured using `cats.effect.Resource`, which guarantees that all resources are
* initialised and cleaned up correctly. This entry point waits for user input to terminate the application, ensuring a
* controlled shutdown.
*/
object Main extends IOApp {

/**
* Configures and manages the lifecycle of all resources and services required for the application.
*
* This method initialises resources such as the actor system, HTTP clients, distributed cache, services, and server.
* It ensures proper resource management by leveraging `Resource` for initialisation and cleanup.
* This method integrates the initialisation and cleanup of:
* - Actor system for concurrency and distributed data.
* - HTTP clients for API communication.
* - Distributed cache services with a configurable time-to-live (TTL) for cache expiration.
* - Core application services (`MechanismService`, `ReactionService`, `ReaktoroService`).
* - HTTP server for hosting API endpoints. API routes are combined and served as a single HTTP application.
*
* The method waits for user input to terminate the application, ensuring a controlled shutdown. All resources are
* composed using `cats.effect.Resource`, ensuring proper cleanup on termination.
*
* @param config
* The configuration loader that provides application-specific settings, such as HTTP endpoints, client
* configurations, and server properties.
* The configuration loader for application-specific settings.
* @param ec
* The `ExecutionContext` used to handle asynchronous operations across the application.
* The `ExecutionContext` for handling asynchronous operations.
* @param system
* The `ActorSystem` used for Akka-based concurrency and distributed data.
* The Akka `ActorSystem` for managing concurrency.
* @param selfUniqueAddress
* The unique address of the current actor system, used for distributed data in a cluster.
* The unique address of the current actor system, used for distributed data.
* @param ttl
* A timeout representing the time-to-live (TTL) for cache expiration.
* @param logger
* An implicit logger instance for recording lifecycle events, debugging, and error handling.
* An implicit logger instance for lifecycle logging.
* @return
* A `Resource[IO, Unit]` that encapsulates the application's full lifecycle. This includes initialisation, running
* the server, and ensuring all resources are properly cleaned up when the application terminates.
* A `Resource[IO, Unit]` that encapsulates the entire lifecycle of the application.
*/
private def runApp(
config: ConfigLoader
Expand All @@ -60,7 +78,7 @@ object Main extends IOApp {
ec: ExecutionContext,
system: ActorSystem,
selfUniqueAddress: SelfUniqueAddress,
cacheExpiration: Timeout,
ttl: Timeout,
logger: Logger[IO]
): Resource[IO, Unit] =
val preprocessorBaseUri = config.preprocessorHttpClientConfig.baseUri
Expand All @@ -86,6 +104,12 @@ object Main extends IOApp {
/**
* Main entry point for the application.
*
* This method sets up the application by:
* - Initialising implicit dependencies, including the logger, actor system, execution context, distributed data,
* unique address, and cache expiration timeout (TTL).
* - Loading configuration using the `DefaultConfigLoader`.
* - Running the application using `runApp` and ensuring all resources are cleaned up on exit.
*
* @param args
* The command-line arguments passed to the application.
* @return
Expand Down
Loading

0 comments on commit 56fa116

Please sign in to comment.