Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 115 additions & 38 deletions docs/customize/code/sdk-hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,42 @@ SDK Hooks enable custom logic to be added to SDK functions and request lifecycle

Hooks can be applied to the following lifecycle events:

- **On SDK initialization:** Modify the base server URL, wrap or override the HTTP client, add tracing, inject global headers, and manage authentication.
- **Before request:** Cancel an outgoing request, transform the request contents, or add tracing.
- **After success:** When a successful response is received, add tracing and logging, validate the response, return an error, or transform the raw response before deserialization.
- **After error:** On connection errors or unsuccessful responses, add tracing and logging or transform the returned error.
- **On SDK initialization:** Modify the SDK configuration, base server URL, wrap or override the HTTP client, add tracing, inject global headers, and manage authentication.
- **Before request:** Cancel an outgoing request, transform the request contents, or add tracing. Access to SDK configuration and operation context.
- **After success:** When a successful response is received, add tracing and logging, validate the response, return an error, or transform the raw response before deserialization. Access to SDK configuration and operation context.
- **After error:** On connection errors or unsuccessful responses, add tracing and logging or transform the returned error. Access to SDK configuration and operation context.

## Hook Context

All hooks (except SDK initialization) receive a `HookContext` object that provides access to:

- **SDK Configuration:** The complete SDK configuration object, allowing hooks to access custom settings, authentication details, and other configuration parameters.
- **Base URL:** The base URL being used for the request.
- **Operation ID:** The unique identifier for the API operation being called.
- **OAuth2 Scopes:** The OAuth2 scopes required for the operation (if applicable).
- **Security Source:** The security configuration or source for the operation.
- **Retry Configuration:** The retry settings for the operation.

## SDK Configuration Access

<Callout title="Important" type="warning">
SDK configuration access in hooks is controlled by the `sdkHooksConfigAccess` feature flag in the `generation` section of your `gen.yaml` configuration file.
</Callout>

The `sdkHooksConfigAccess` feature flag determines whether hooks have access to the complete SDK configuration object:

- **`sdkHooksConfigAccess: true`** (default for newly generated SDKs): Hooks receive full access to the SDK configuration through the `HookContext` object, and the SDK initialization hook receives the complete configuration object as a parameter.

- **`sdkHooksConfigAccess: false`** (default for SDKs generated before May 2025): Hooks have limited access to SDK configuration, and the SDK initialization hook has a different signature that doesn't include the configuration parameter.

### Version Compatibility

- **New SDKs (May 2025 and later)**: The `sdkHooksConfigAccess` flag defaults to `true`, providing full configuration access.
- **Existing SDKs (before May 2025)**: The flag defaults to `false` to maintain backward compatibility. You can manually set it to `true` in your `gen.yaml` file to enable full configuration access.

When `sdkHooksConfigAccess` is set to `false`, the SDK initialization hook will have a different signature that doesn't receive the configuration object as a parameter, limiting the customization options available during SDK initialization.

To enable full SDK configuration access in existing SDKs, add `sdkHooksConfigAccess: true` under the `generation` section in your `gen.yaml` file.

## Add a Hook

Expand Down Expand Up @@ -180,22 +212,29 @@ var (
_ afterErrorHook = (*ExampleHook)(nil)
)

func (i *ExampleHook) SDKInit(baseURL string, client HTTPClient) (string, HTTPClient) {
// modify the baseURL or wrap the client used by the SDK here and return the updated values
return baseURL, client
func (i *ExampleHook) SDKInit(config SDKConfig) SDKConfig {
// modify the SDK configuration, baseURL, or wrap the client used by the SDK here and return the updated config
// Access config.BaseURL, config.Client, and other configuration options
return config
}

func (i *ExampleHook) BeforeRequest(hookCtx BeforeRequestContext, req *http.Request) (*http.Request, error) {
// Access SDK configuration: hookCtx.Config
// Access operation details: hookCtx.OperationID, hookCtx.BaseURL
// modify the request object before it is sent, such as adding headers or query parameters, or return an error to stop the request from being sent
return req, nil
}

func (i *ExampleHook) AfterSuccess(hookCtx AfterSuccessContext, res *http.Response) (*http.Response, error) {
// Access SDK configuration: hookCtx.Config
// Access operation details: hookCtx.OperationID, hookCtx.BaseURL
// modify the response object before deserialization or return an error to stop the response from being deserialized
return res, nil
}

func (i *ExampleHook) AfterError(hookCtx AfterErrorContext, res *http.Response, err error) (*http.Response, error) {
// Access SDK configuration: hookCtx.Config
// Access operation details: hookCtx.OperationID, hookCtx.BaseURL
// modify the response before it is deserialized as a custom error or the error object before it is returned or return an error wrapped in the FailEarly error in this package to exit from the hook chain early
return res, err
}
Expand All @@ -213,22 +252,25 @@ from ..types import (
AfterSuccessHook,
BeforeRequestContext,
BeforeRequestHook,
HttpClient,
SDKConfiguration,
SDKInitHook,
)


class ExampleHook(SDKInitHook, BeforeRequestHook, AfterSuccessHook, AfterErrorHook):

def sdk_init(self, base_url: str, client: HttpClient) -> Tuple[str, HttpClient]:
# modify the base_url or wrap the client used by the SDK here and return the
# updated values
def sdk_init(self, config: SDKConfiguration) -> SDKConfiguration:
# modify the SDK configuration, base_url, or wrap the client used by the SDK here and return the
# updated configuration
# Access config.base_url, config.client, and other configuration options

return base_url, client
return config

def before_request(
self, hook_ctx: BeforeRequestContext, request: httpx.Request
) -> Union[httpx.Request, Exception]:
# Access SDK configuration: hook_ctx.config
# Access operation details: hook_ctx.operation_id, hook_ctx.base_url
# modify the request object before it is sent, such as adding headers or query
# parameters, or raise an exception to stop the request

Expand All @@ -237,6 +279,8 @@ class ExampleHook(SDKInitHook, BeforeRequestHook, AfterSuccessHook, AfterErrorHo
def after_success(
self, hook_ctx: AfterSuccessContext, response: httpx.Response
) -> Union[httpx.Response, Exception]:
# Access SDK configuration: hook_ctx.config
# Access operation details: hook_ctx.operation_id, hook_ctx.base_url
# modify the response object before deserialization or raise an exception to stop
# the response from being returned

Expand All @@ -248,6 +292,8 @@ class ExampleHook(SDKInitHook, BeforeRequestHook, AfterSuccessHook, AfterErrorHo
response: Optional[httpx.Response],
error: Optional[Exception],
) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]:
# Access SDK configuration: hook_ctx.config
# Access operation details: hook_ctx.operation_id, hook_ctx.base_url
# modify the response before it is deserialized as a custom error or the error
# object before it is returned or raise an exception to stop processing of other
# error hooks and return early
Expand All @@ -256,7 +302,7 @@ class ExampleHook(SDKInitHook, BeforeRequestHook, AfterSuccessHook, AfterErrorHo
```

```typescript !!tabs TypeScript
import { HTTPClient } from "../lib/http";
import { SDKOptions } from "../lib/config";
import {
AfterErrorContext,
AfterErrorHook,
Expand All @@ -265,25 +311,27 @@ import {
BeforeRequestContext,
BeforeRequestHook,
SDKInitHook,
SDKInitOptions,
} from "./types";

export class ExampleHook
implements SDKInitHook, BeforeRequestHook, AfterSuccessHook, AfterErrorHook
{
sdkInit(opts: SDKInitOptions): SDKInitOptions {
const { baseURL, client } = opts;

// modify the baseURL or wrap the client used by the SDK here and return the updated values
return { baseURL: baseURL, client: client };
sdkInit(opts: SDKOptions): SDKOptions {
// modify the SDK configuration, baseURL, or wrap the client used by the SDK here and return the updated options
// Access opts.baseURL, opts.client, and other configuration options
return opts;
}

beforeRequest(hookCtx: BeforeRequestContext, request: Request): Request {
// Access SDK configuration: hookCtx.options
// Access operation details: hookCtx.operationID, hookCtx.baseURL
// modify the request object before it is sent, such as adding headers or query parameters, or throw an error to stop the request from being sent
return request;
}

afterSuccess(hookCtx: AfterSuccessContext, response: Response): Response {
// Access SDK configuration: hookCtx.options
// Access operation details: hookCtx.operationID, hookCtx.baseURL
// modify the response object before deserialization or throw an error to stop the response from being deserialized
return response;
}
Expand All @@ -293,6 +341,8 @@ export class ExampleHook
response: Response | null,
error: unknown,
): { response: Response | null; error: unknown } {
// Access SDK configuration: hookCtx.options
// Access operation details: hookCtx.operationID, hookCtx.baseURL
// modify the response before it is deserialized as a custom error or the error object before it is returned or throw an error to stop processing of other error hooks and return early
return { response, error };
}
Expand All @@ -310,7 +360,7 @@ import dev.speakeasyapi.speakeasy.utils.Hook.AfterSuccessContext;
import dev.speakeasyapi.speakeasy.utils.Hook.BeforeRequest;
import dev.speakeasyapi.speakeasy.utils.Hook.BeforeRequestContext;
import dev.speakeasyapi.speakeasy.utils.Hook.SdkInit;
import dev.speakeasyapi.speakeasy.utils.Hook.SdkInitData;
import dev.speakeasyapi.speakeasy.SDKConfiguration;

import java.io.InputStream;
import java.net.http.HttpRequest;
Expand All @@ -320,13 +370,16 @@ import java.util.Optional;
final class ExampleHook implements BeforeRequest, AfterError, AfterSuccess, SdkInit {

@Override
public SdkInitData sdkInit(SdkInitData data) {
// modify the baseURL or wrap the client used by the SDK here and return the updated values
return new SdkInitData(data.baseUrl(), data.client());
public SDKConfiguration sdkInit(SDKConfiguration config) {
// modify the SDK configuration, baseURL, or wrap the client used by the SDK here and return the updated config
// Access config properties and modify as needed
return config;
}

@Override
public HttpRequest beforeRequest(BeforeRequestContext context, HttpRequest request) throws Exception {
// Access SDK configuration: context.sdkConfiguration()
// Access operation details: context.operationId(), context.baseUrl()
// modify the request object before it is sent, such as adding headers or query parameters
// or throw an error to stop the request from being sent

Expand All @@ -341,6 +394,8 @@ final class ExampleHook implements BeforeRequest, AfterError, AfterSuccess, SdkI
@Override
public HttpResponse<InputStream> afterSuccess(AfterSuccessContext context, HttpResponse<InputStream> response)
throws Exception {
// Access SDK configuration: context.sdkConfiguration()
// Access operation details: context.operationId(), context.baseUrl()
// modify the response object before deserialization or throw an exception to stop the
// response from being deserialized
return response;
Expand All @@ -349,10 +404,12 @@ final class ExampleHook implements BeforeRequest, AfterError, AfterSuccess, SdkI
@Override
public HttpResponse<InputStream> afterError(AfterErrorContext context,
Optional<HttpResponse<InputStream>> response, Optional<Exception> error) throws Exception {
// Access SDK configuration: context.sdkConfiguration()
// Access operation details: context.operationId(), context.baseUrl()
// modify the response before it is deserialized as a custom error or the exception
// object before it is thrown or throw a FailEarlyException to stop processing of
// other error hooks and return early
return response;
return response.orElse(null);
}
}
```
Expand All @@ -365,26 +422,33 @@ namespace Speakeasy.Hooks

public class ExampleHook : ISDKInitHook, IBeforeRequestHook, IAfterSuccessHook, IAfterErrorHook
{
public (string, ISpeakeasyHttpClient) SDKInit(string baseURL, ISpeakeasyHttpClient client)
public SDKConfig SDKInit(SDKConfig config)
{
// modify the baseURL or wrap the client used by the SDK here and return the updated values
return (baseURL, client);
// modify the SDK configuration, baseURL, or wrap the client used by the SDK here and return the updated config
// Access config.BaseURL, config.Client, and other configuration options
return config;
}

public async Task<HttpRequestMessage> BeforeRequestAsync(BeforeRequestContext hookCtx, HttpRequestMessage request)
{
// Access SDK configuration: hookCtx.SDKConfiguration
// Access operation details: hookCtx.OperationID, hookCtx.BaseURL
// modify the request object before it is sent, such as adding headers or query parameters, or throw an exception to stop the request from being sent
return request;
}

public async Task<HttpResponseMessage> AfterSuccessAsync(AfterSuccessContext hookCtx, HttpResponseMessage response)
{
// Access SDK configuration: hookCtx.SDKConfiguration
// Access operation details: hookCtx.OperationID, hookCtx.BaseURL
// modify the response object before deserialization or throw an exception to stop the response from being returned
return response;
}

public async Task<(HttpResponseMessage?, Exception?)> AfterErrorAsync(AfterErrorContext hookCtx, HttpResponseMessage? response, Exception error)
{
// Access SDK configuration: hookCtx.SDKConfiguration
// Access operation details: hookCtx.OperationID, hookCtx.BaseURL
// modify the response before it is deserialized as a custom error
// return (response, null);

Expand All @@ -407,26 +471,33 @@ namespace Speakeasy\Hooks;

class ExampleHook implements AfterErrorHook, AfterSuccessHook, BeforeRequestHook, SDKInitHook
{
public function sdkInit(string $baseUrl, \GuzzleHttp\ClientInterface $client): SDKRequestContext
public function sdkInit(SDKConfiguration $config): SDKConfiguration
{
// modify the baseURL or wrap the client used by the SDK here and return the updated values
return new SDKRequestContext($baseUrl, new TestClient($client));
// modify the SDK configuration, baseURL, or wrap the client used by the SDK here and return the updated config
// Access config properties and modify as needed
return $config;
}

public function beforeRequest(BeforeRequestContext $context, RequestInterface $request): RequestInterface
{
// Access SDK configuration: $context->config
// Access operation details: $context->operationID, $context->baseURL
// modify the request object before it is sent, such as adding headers or query parameters, or throw an exception to stop the request from being sent
return $request;
}

public function afterSuccess(AfterSuccessContext $context, ResponseInterface $response): ResponseInterface
{
// Access SDK configuration: $context->config
// Access operation details: $context->operationID, $context->baseURL
// modify the response object before deserialization or throw an exception to stop the response from being returned
return $response;
}

public function afterError(AfterErrorContext $context, ?ResponseInterface $response, ?\Throwable $exception): ErrorResponseContext
{
// Access SDK configuration: $context->config
// Access operation details: $context->operationID, $context->baseURL
// modify the response before it is deserialized as a custom error
// return new ErrorResponseContext($response, null);

Expand All @@ -438,7 +509,7 @@ class ExampleHook implements AfterErrorHook, AfterSuccessHook, BeforeRequestHook

// response and error cannot both be null
return new ErrorResponseContext($response, $exception);
}
}
}

```
Expand All @@ -461,15 +532,15 @@ module ExampleSDK

sig do
override.params(
base_url: String,
client: Faraday::Connection
).returns([String, Faraday::Connection])
config: SDKConfiguration
).returns(SDKConfiguration)
end
def sdk_init(base_url:, client:)
# modify the base_url or wrap the client used by the SDK here and return
# the updated values
def sdk_init(config:)
# modify the SDK configuration, base_url, or wrap the client used by the SDK here and return
# the updated configuration
# Access config properties and modify as needed

return base_url, client
config
end

sig do
Expand All @@ -479,6 +550,8 @@ module ExampleSDK
).returns(Faraday::Request)
end
def before_request(hook_ctx:, request:)
# Access SDK configuration: hook_ctx.config
# Access operation details: hook_ctx.operation_id, hook_ctx.base_url
# modify the request object before it is sent, such as adding headers or
# query parameters, or raise an exception to stop the request

Expand All @@ -492,6 +565,8 @@ module ExampleSDK
).returns(Faraday::Response)
end
def after_success(hook_ctx:, response:)
# Access SDK configuration: hook_ctx.config
# Access operation details: hook_ctx.operation_id, hook_ctx.base_url
# modify the response object before deserialization or raise an
# exception to stop the response from being returned

Expand All @@ -506,6 +581,8 @@ module ExampleSDK
).returns(T.nilable(Faraday::Response))
end
def after_error(error:, hook_ctx:, response:)
# Access SDK configuration: hook_ctx.config
# Access operation details: hook_ctx.operation_id, hook_ctx.base_url
# modify the response before it is deserialized or raise an exception to
# stop processing of other error hooks and return early

Expand Down
Loading