Skip to main content

Back to the liblab blog

How to Customize your SDKs using Hooks

| 7 min

Hooks are an elegant way to extend and tailor your SDKs to meet specific needs. By using hooks, you can intercept and modify API requests and responses, or even embed additional functionality. Whether you're adding custom headers, tweaking authentication, or enhancing logging, hooks put you in control of the SDK’s behavior.

To better understand this blog post and follow its instructions, check out our documentation on adding hooks to your liblab project.

How Hooks Are Designed

Hooks in the SDK are designed as flexible extension points that allow you to tap into key moments in the lifecycle of an API call. These moments are represented by three primary methods:

1. Before Request

The before_request method is called before the API request is sent. It allows you to modify the request object by, for example:

  • Adding or removing headers.
  • Changing the request URL.
  • Updating the request body.

This method is ideal for scenarios like injecting API versions, custom headers, or tweaking authentication mechanisms.

Example (Java):

@Override
public Request beforeRequest(Request request, Map<String, String> additionalParameters) {
// Add a custom header to the request
return request.newBuilder()
.addHeader("Custom-Header", "value")
.build();
}

2. After Response

The after_response method is invoked after the API call has completed, but before the response is returned to the user. You can use this method to, for example:

  • Parse or transform the response data;
  • Add custom logging for successful API calls.

Example (Typescript)

public async afterResponse(
request: HttpRequest,
response: HttpResponse<any>,
params: Map<string, string>
): Promise<HttpResponse<any>>{
console.log(`Response received: ${response.status}`);
return response;
}

3. On Error

The on_error method is triggered when an API call fails. It provides a chance to:

  • Improve the error information;
  • Log errors for debugging purposes.

Example (Python)

def on_error(self, error: Exception, request: Request, response: Response, **kwargs):
print(f"API Error: {exception}")

Extending Hooks with Dependencies

Hooks provide a powerful way to enhance your SDK functionality, and sometimes this requires additional libraries or tools. To include dependencies in your hooks, you need to declare them in your liblab config file. This ensures the dependencies are correctly packaged during the SDK generation process.

Example: Using a Dependency in beforeRequest (Go)

Let’s demonstrate how to use a dependency in the beforeRequest method. In this example, we’ll use the uuid package to generate a unique request ID and include it as a custom header.

  1. Update the liblab Config File

    Add the dependency to the hookDependencies section of the desired language (in this case go) on the liblab.config.json file:

    {
    ...
    "languageOptions": {
    "go": {
    "hookDependencies": [
    {
    "name": "github.com/google/uuid",
    "version": "v1.3.0"
    }
    ]
    }
    }
    ...
    }
  2. Modify the Hook Implementation

    Update the CustomHook implementation to generate and include a UUID in the BeforeRequest method:

    package hooks

    import (
    "fmt"
    "github.com/google/uuid"
    )

    type CustomHook struct{}

    func NewCustomHook() Hook {
    return &CustomHook{}
    }

    func (h *CustomHook) BeforeRequest(req Request, params map[string]string) Request {
    // Generate a unique UUID
    requestID := uuid.New().String()

    // Add the UUID as a custom header
    req.SetHeader("X-Request-ID", requestID)

    fmt.Printf("BeforeRequest: Added X-Request-ID: %s\n", requestID)
    return req
    }
  3. Rebuild the SDK

    After updating the hook and adding the dependency, rebuild the SDK:

    liblab build

Using dependencies in hooks opens up possibilities for enhancing your SDK with additional functionality like telemetry, advanced validation, or logging frameworks.

Extending Hooks with Additional Constructor Parameters

You can extend your hooks by adding additional constructor parameters that can be passed into the hooks’ methods. This functionality allows you to inject dynamic values (like encryption keys or logging configurations) into the SDK through the client at runtime. To do so, you need to configure the parameters in your liblab config file, which are then made available to your hooks.

Example: Decrypting Response Body with Additional Parameters (C#)

  1. Config File: Add the parameters EncryptionKey and EncryptionAlgorithm to the additionalConstructorParameters section in your config file:

    {
    "languageOptions": {
    "csharp": {
    "additionalConstructorParameters": [
    {
    "name": "EncryptionKey",
    "example": "mySecretKey"
    },
    {
    "name": "EncryptionAlgorithm",
    "example": "AES256"
    }
    ]
    }
    }
    }
  2. Custom Hook (C#):

    In your hook implementation, access the parameters passed to the hook methods via the additionalParameters dictionary. Use them, for instance, to decrypt a response body:

    public class CustomHook : IHook
    {
    ...
    public async Task<HttpResponseMessage> AfterResponseAsync(
    HttpResponseMessage response,
    Dictionary<string, string?> additionalParameters
    )
    {
    // Get the EncryptionKey and EncryptionAlgorithm from the parameters
    string encryptionKey = additionalParameters["EncryptionKey"];
    string encryptionAlgorithm = additionalParameters["EncryptionAlgorithm"];

    // Decrypt the response body using the provided parameters
    string decryptedBody = DecryptResponseBody(
    response.Content, encryptionKey, encryptionAlgorithm
    );
    response.Content = StringContent.Create(decryptedBody);

    return await Task.FromResult(response);
    }
    ...

    private string DecryptResponseBody(
    HttpContent content,
    string encryptionKey,
    string encryptionAlgorithm
    )
    {
    // Here you would decrypt the content based on the algorithm and key
    return $"Decrypted content using {encryptionAlgorithm} with key: {encryptionKey}";
    }
    }
  3. Rebuild the SDK

    After updating the hook, rebuild the SDK:

    liblab build

This feature is perfect for scenarios where your API requests or responses need to be customized with external values such as encryption keys, API tokens, or other dynamic configurations.

Use Cases for Hooks

Hooks provide incredible versatility, enabling you to tailor your SDK to a variety of real-world scenarios. Below are some use cases to inspire how you might leverage hooks in your projects:

1. Authentication

While SDKs often support standard authentication methods out of the box (you can check a list of the supported methods in our documentation), hooks enable you to handle more complex authentication flows, specially if you combine them with additional constructor parameters.

This flexibility ensures your SDK can work seamlessly with a variety of authentication schemes.

2. Logging Requests and Responses

Enhance visibility into your API interactions by logging request details and response data. This is especially useful for debugging, monitoring, and auditing API usage. Make sure to combine it with hook dependencies to use your favorite logger.

3. Request Throttling

Implement rate-limiting logic to ensure your SDK doesn’t exceed API quotas. This can be crucial for managing costs and preventing throttling errors.

4. Data Encryption/Decryption

Secure sensitive data by encrypting request payloads or decrypting response data. Hooks can integrate seamlessly with encryption libraries or custom security solutions.

5. Injecting Correlation IDs for Distributed Tracing

Generate and inject unique correlation IDs into requests for distributed tracing. This is essential for tracking API calls across micro-services in complex systems.

6. Request Metrics Collection

Gather performance metrics such as request duration, response times, and error rates. You can use these metrics for monitoring API health and identifying bottlenecks.

7. Feature Flag Management

Enable dynamic feature toggling by injecting or altering parameters based on feature flags. This allows your SDK to support A/B testing and phased rollouts.

8. Conditional Proxy Routing

Dynamically route API requests through different proxies or endpoints based on custom conditions. This is helpful for supporting multiple environments or handling failover scenarios.

9. Automatic Compression

Optimize network usage by compressing request payloads or decompressing responses on the fly. This can significantly improve performance for large payloads.

10. Dynamic Localization of Requests

Use hooks to localize requests dynamically based on user or system preferences. For example, you can:

  • Add headers to specify language preferences.
  • Route requests to region-specific endpoints.
  • Modify payloads to include localized data.

This is particularly useful for global applications that need to cater to users in different languages or regions.

Conclusion

Hooks unlock a world of possibilities for customizing your SDKs, making them adaptable to even the most complex and unique requirements. Whether you're streamlining authentication flows, enhancing observability, or enabling dynamic localization, hooks give you the flexibility to make your SDK truly your own.

We’re excited for you to try out hooks in liblab and see how they can simplify and enhance your development experience. For step-by-step guides and additional resources, don’t forget to visit our documentation.

Have questions or need help? Join our Discord community to connect with other developers and the liblab team. We’d love to hear about your use cases and help you get the most out of hooks. See you there!

Best Practices

API

Software Engineering

Hooks