Hone logo
Hone
Problems

Go Request Pipeline

This challenge involves building a flexible request pipeline in Go. You'll create a system where incoming requests can be processed by a series of middleware functions. This is a common pattern in web frameworks for tasks like authentication, logging, request validation, and more, allowing for clean and modular request handling.

Problem Description

You need to implement a Pipeline struct in Go that can execute a sequence of middleware functions. Each middleware function should receive a Request and a Handler (representing the next step in the pipeline). The middleware can then perform some operation, and either call the next handler, short-circuit the pipeline by returning a response directly, or modify the request before passing it to the next handler.

Key Requirements:

  1. Request Struct: Define a Request struct to hold data that is passed through the pipeline. It should be a simple struct for demonstration purposes.
  2. Response Struct: Define a Response struct to represent the result of the pipeline execution.
  3. Handler Type: Define a function type Handler that takes a Request and returns a Response. This represents the final processing step or the next middleware in the chain.
  4. Middleware Type: Define a function type Middleware that takes a Request and a Handler, and returns a Handler. This function wraps the Handler it receives, allowing it to add its own logic before or after calling the wrapped handler.
  5. Pipeline Struct:
    • It should hold a slice of Middleware functions.
    • It should have a method to add new middleware (Use method).
    • It should have a method to execute the pipeline (Execute method). This method will take an initial Request and a final Handler (the "endpoint" handler that runs if the pipeline completes without short-circuiting).
  6. Execution Flow: When Execute is called, the Request should be passed through each Middleware in the order they were added. Each middleware will wrap the next handler in the chain. If a middleware decides to return a Response early (short-circuit), that Response should be returned immediately by Execute. Otherwise, the pipeline will eventually reach the final Handler provided to Execute.

Expected Behavior:

  • Middleware functions can inspect and modify the Request.
  • Middleware functions can decide to pass the Request to the next Handler in the pipeline.
  • Middleware functions can decide to short-circuit the pipeline and return a Response directly, without calling the next Handler.
  • The final Handler is only called if no middleware short-circuits the pipeline.

Edge Cases:

  • An empty pipeline: If no middleware is added, the Execute method should directly call the provided final Handler.
  • Multiple middleware short-circuiting: The first middleware that short-circuits should determine the final Response.

Examples

Example 1:

Input:
Request: {ID: "req-1", Data: "initial data"}
Middleware 1: Logs the request data.
Middleware 2: Adds a new field "processed" to the request.
Final Handler: Returns a Response with the processed data.

Output:
Response: {Status: "Success", Message: "Processed data: initial data, processed", Processed: true}
Explanation:
Middleware 1 logs "req-1: initial data".
Middleware 2 receives {ID: "req-1", Data: "initial data"}, modifies it to {ID: "req-1", Data: "initial data", Processed: true}.
The final handler receives the modified request and generates the success response.

Example 2:

Input:
Request: {ID: "req-2", Data: "sensitive data"}
Middleware 1: Checks for a specific "admin" flag in the request. If not present, short-circuits with an error.
Middleware 2: (Will not be reached)
Final Handler: (Will not be reached)

Output:
Response: {Status: "Error", Message: "Unauthorized access"}
Explanation:
Middleware 1 receives the request. It checks for an "admin" flag (which is not present). It decides to short-circuit and returns an error response immediately. Middleware 2 and the final handler are never called.

Example 3: (Empty Pipeline)

Input:
Request: {ID: "req-3", Data: "plain data"}
Pipeline: (empty)
Final Handler: Returns a Response indicating the data.

Output:
Response: {Status: "OK", Message: "Data is: plain data", Processed: false}
Explanation:
Since the pipeline is empty, the `Execute` method directly calls the final handler with the initial request.

Constraints

  • The Request struct can have at least two fields: ID (string) and Data (string).
  • The Response struct can have at least three fields: Status (string), Message (string), and Processed (bool).
  • The number of middleware functions in a pipeline can be up to 50.
  • The execution time of each individual middleware and the final handler should be reasonably fast (e.g., negligible for this challenge's scope, assuming no I/O).
  • Input Request and Response structs will conform to the defined structures.

Notes

  • Think about how you can compose the Handler functions using your middleware. This is the core of the pipeline pattern.
  • The Middleware function signature func(Request) Handler is crucial. It allows each middleware to return a new Handler that incorporates its logic.
  • Consider using functional options pattern if you want to make adding middleware more configurable, but for this challenge, a simple Use method is sufficient.
  • The order of middleware execution is critical.
Loading editor...
go