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:
RequestStruct: Define aRequeststruct to hold data that is passed through the pipeline. It should be a simple struct for demonstration purposes.ResponseStruct: Define aResponsestruct to represent the result of the pipeline execution.HandlerType: Define a function typeHandlerthat takes aRequestand returns aResponse. This represents the final processing step or the next middleware in the chain.MiddlewareType: Define a function typeMiddlewarethat takes aRequestand aHandler, and returns aHandler. This function wraps theHandlerit receives, allowing it to add its own logic before or after calling the wrapped handler.PipelineStruct:- It should hold a slice of
Middlewarefunctions. - It should have a method to add new middleware (
Usemethod). - It should have a method to execute the pipeline (
Executemethod). This method will take an initialRequestand a finalHandler(the "endpoint" handler that runs if the pipeline completes without short-circuiting).
- It should hold a slice of
- Execution Flow: When
Executeis called, theRequestshould be passed through eachMiddlewarein the order they were added. Each middleware will wrap the next handler in the chain. If a middleware decides to return aResponseearly (short-circuit), thatResponseshould be returned immediately byExecute. Otherwise, the pipeline will eventually reach the finalHandlerprovided toExecute.
Expected Behavior:
- Middleware functions can inspect and modify the
Request. - Middleware functions can decide to pass the
Requestto the nextHandlerin the pipeline. - Middleware functions can decide to short-circuit the pipeline and return a
Responsedirectly, without calling the nextHandler. - The final
Handleris only called if no middleware short-circuits the pipeline.
Edge Cases:
- An empty pipeline: If no middleware is added, the
Executemethod should directly call the provided finalHandler. - 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
Requeststruct can have at least two fields:ID(string) andData(string). - The
Responsestruct can have at least three fields:Status(string),Message(string), andProcessed(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
RequestandResponsestructs will conform to the defined structures.
Notes
- Think about how you can compose the
Handlerfunctions using your middleware. This is the core of the pipeline pattern. - The
Middlewarefunction signaturefunc(Request) Handleris crucial. It allows each middleware to return a newHandlerthat incorporates its logic. - Consider using functional options pattern if you want to make adding middleware more configurable, but for this challenge, a simple
Usemethod is sufficient. - The order of middleware execution is critical.