Hone logo
Hone
Problems

Implementing Request Interceptors in Go

Interceptors are a powerful pattern for adding cross-cutting concerns to function calls, such as logging, authentication, or metrics collection. In this challenge, you will implement a flexible interceptor mechanism for a hypothetical service in Go. This will allow you to wrap function calls with custom logic that executes before and after the main function's execution.

Problem Description

You are tasked with building an interceptor system for a simple "UserService" in Go. The system should allow for multiple interceptors to be chained together, executing in a defined order. Each interceptor should have the opportunity to modify the request, the response, or abort the execution flow.

Key Requirements:

  1. Define an Interceptor Interface: Create an interface that represents an interceptor. This interface should define a method that takes a request, a response, and a handler for the next interceptor (or the actual service method) in the chain.
  2. Implement a Chaining Mechanism: Develop a way to chain multiple interceptors together. When a request is made, the interceptors should be executed sequentially.
  3. Handle Request and Response: Interceptors should be able to inspect and potentially modify the request before it reaches the next handler. They should also be able to inspect and potentially modify the response after the next handler has executed.
  4. Allow Aborting Execution: An interceptor should be able to decide to stop the execution chain and return an error or a specific response.
  5. Integrate with a Service: Demonstrate how this interceptor system can be used to wrap calls to a simple UserService.

Expected Behavior:

  • When a method on UserService is called, the interceptor chain should be invoked.
  • Each interceptor's "before" logic should execute in the order they are registered.
  • The final interceptor (or the original service method if no interceptors) will execute.
  • Each interceptor's "after" logic should execute in reverse order of their registration.
  • If an interceptor decides to abort, no subsequent interceptors or the original service method should be called, and the error/response from the aborting interceptor should be returned.

Edge Cases:

  • No interceptors registered.
  • An interceptor modifies the request, and this modification should be visible to subsequent interceptors and the service method.
  • An interceptor modifies the response, and this modification should be visible to preceding interceptors in the "after" phase.
  • An interceptor aborts the chain.

Examples

Example 1: Basic Logging Interceptor

Input: A call to UserService.GetUser with UserID: 123. The interceptor chain contains a LoggingInterceptor.

Output:

INFO: Calling GetUser with UserID: 123
INFO: GetUser executed successfully.
{UserID: 123, Name: "Alice"}

Explanation: The LoggingInterceptor intercepts the call. Its "before" logic logs that GetUser is being called. The actual GetUser method executes and returns a user. The LoggingInterceptor's "after" logic then logs that the execution was successful before returning the user.

Example 2: Authentication Interceptor that Aborts

Input: A call to UserService.UpdateUser with UserID: 456 and AuthToken: "invalid_token". The interceptor chain contains an AuthInterceptor and a LoggingInterceptor.

Output:

INFO: Calling UpdateUser with UserID: 456
ERROR: Authentication failed. Invalid token.

Explanation: The LoggingInterceptor executes its "before" logic. Then, the AuthInterceptor checks the AuthToken. Since it's invalid, the AuthInterceptor aborts the chain by returning an error. The UserService.UpdateUser method is never called, and the LoggingInterceptor's "after" logic is also not executed.

Example 3: Request Modification Interceptor

Input: A call to UserService.CreateUser with Name: "Bob". The interceptor chain contains an EnrichmentInterceptor that adds a default email.

Output:

INFO: Processing CreateUser request for name: Bob
INFO: User created successfully with ID: 789, Name: Bob, Email: bob@example.com
{UserID: 789, Name: "Bob", Email: "bob@example.com"}

Explanation: The LoggingInterceptor executes its "before" logic. The EnrichmentInterceptor sees that the user's email is missing and adds a default email to the request. The UserService.CreateUser method then processes the request with the modified email. Finally, the LoggingInterceptor logs the successful creation.

Constraints

  • The interceptor mechanism should be generic enough to handle different request and response types for various service methods. Consider using interface{} or generics if appropriate for your Go version.
  • The order of execution for interceptors is critical and must be strictly adhered to.
  • Error handling should be robust, allowing interceptors to return specific errors that propagate up the call chain.
  • Performance is a consideration; the overhead of the interceptor chain should be minimized.

Notes

  • Think about how to pass the "next" handler in the chain to each interceptor.
  • Consider how to represent the actual service method as a "handler" that an interceptor can call.
  • You might want to define a Handler type that represents the function to be called next in the chain.
  • For UserService, you can define simple User and CreateUserRequest, GetUserRequest, etc., structs.
  • Consider using Go's context package to pass request-scoped values and deadlines through the interceptor chain.
Loading editor...
go