Hone logo
Hone
Problems

Request Pipeline Implementation in Go

This challenge asks you to implement a request pipeline in Go, a common pattern for processing data sequentially through a series of functions. Request pipelines are useful for tasks like data validation, transformation, and enrichment, where each stage performs a specific operation on the incoming data. Successfully completing this challenge demonstrates your understanding of functional programming principles and Go's concurrency capabilities.

Problem Description

You are tasked with creating a generic request pipeline in Go. The pipeline should accept a request of any type and pass it through a series of processing functions (stages). Each stage should perform a specific operation on the request and return the modified request to the next stage. The final stage should return the processed request.

Key Requirements:

  • Generic Request Type: The pipeline should be able to handle requests of any type. Use Go's interface{} to achieve this.
  • Modular Stages: Stages should be defined as functions that accept and return the generic request type.
  • Sequential Processing: Requests should be processed sequentially through the defined stages.
  • Error Handling: Each stage should be able to return an error. If any stage returns an error, the pipeline should immediately stop processing and return the error.
  • Pipeline Definition: The pipeline should be configurable, allowing users to define the order of stages.

Expected Behavior:

The Pipeline struct should have a Process method that takes a request (of type interface{}) and a slice of stage functions. The Process method should iterate through the stages, applying each stage to the request. If a stage returns an error, the Process method should return the error immediately. If all stages complete successfully, the Process method should return the final processed request and a nil error.

Edge Cases to Consider:

  • Empty Pipeline: What should happen if the pipeline is empty (no stages defined)? Should it return the original request and a nil error, or an error?
  • Nil Request: What should happen if the input request is nil? Should it return an error, or pass it through?
  • Error Handling in Stages: Ensure that errors returned by stages are properly propagated and handled.

Examples

Example 1:

Input: request := "Initial Request"
stages := []func(interface{}) (interface{}, error){
    func(req interface{}) (interface{}, error) {
        reqStr := req.(string)
        return reqStr + " - Stage 1", nil
    },
    func(req interface{}) (interface{}, error) {
        reqStr := req.(string)
        return reqStr + " - Stage 2", nil
    },
}
Output: "Initial Request - Stage 1 - Stage 2"
Explanation: The request is passed through two stages, each appending a string.

Example 2:

Input: request := 123
stages := []func(interface{}) (interface{}, error){
    func(req interface{}) (interface{}, error) {
        reqInt := req.(int)
        return reqInt * 2, nil
    },
    func(req interface{}) (interface{}, error) {
        reqInt := req.(int)
        return reqInt + 10, nil
    },
}
Output: 244
Explanation: The request (an integer) is passed through two stages, doubling it and then adding 10.

Example 3: (Error Handling)

Input: request := "Some Request"
stages := []func(interface{}) (interface{}, error){
    func(req interface{}) (interface{}, error) {
        reqStr := req.(string)
        if reqStr == "Error Request" {
            return nil, fmt.Errorf("Error occurred in Stage 1")
        }
        return reqStr + " - Stage 1", nil
    },
    func(req interface{}) (interface{}, error) {
        reqStr := req.(string)
        return reqStr + " - Stage 2", nil
    },
}
Output: Error occurred in Stage 1
Explanation: Stage 1 detects the "Error Request" and returns an error, causing the pipeline to stop.

Constraints

  • The pipeline should be able to handle requests of any type.
  • Each stage function should accept an interface{} and return an interface{}, error.
  • The pipeline should be thread-safe (although concurrency is not explicitly required for this challenge, consider it for robustness).
  • The solution should be well-documented and easy to understand.
  • The solution should be efficient and avoid unnecessary allocations.

Notes

  • Consider using Go's interface{} to achieve generic request handling.
  • Think about how to define the stages in a flexible and reusable way.
  • Pay close attention to error handling and ensure that errors are propagated correctly.
  • While not required, consider how you might extend this pipeline to support concurrency (e.g., processing stages in parallel). This is a good exercise for thinking about future extensibility.
  • The fmt package is available for error formatting.
  • Focus on creating a clean and well-structured solution that adheres to Go's best practices.
Loading editor...
go