Adding discriminators with hooks

This guide explains how to use SDK hooks to handle polymorphic responses in your API by injecting discriminators.

What are discriminators?

In OpenAPI, discriminators allow you to specify which schema to use for a particular response based on a property value. This is useful when an API endpoint can return different types of objects, and the client needs to handle each type differently.

Why use hooks with discriminators?

Sometimes your API may not include discriminator fields in the response. Using SDK hooks, you can inject these discriminators into responses before they are processed by the SDK, allowing for proper polymorphic deserialization.

Setting up discriminators with an overlay file

First, you’ll need to define the discriminator fields in your OpenAPI schema. You can do this with an overlay file:

overlay: 1.0.0
x-speakeasy-jsonpath: rfc9535
info:
title: example overlay
version: 0.0.0
actions:
- target: $
update:
openapi: 3.0.3
# First, set up a discriminator field on each model that needs it.
- target: $.components.schemas
update:
ResponseType:
type: string
enum: [TypeA, TypeB, TypeC]
x-speakeasy-include: true
- target: $.components.schemas.TypeA
update:
required: ['_type']
properties:
_type:
const: TypeA
- target: $.components.schemas.TypeB
update:
required: ['_type']
properties:
_type:
const: TypeB
- target: $.components.schemas.TypeC
update:
required: ['_type']
properties:
_type:
const: TypeC
# Next, we'll remove all of the 2XX responses so we can add a new catch-all success response.
- target: $.paths["/resource"].post.responses['200']
remove: true
- target: $.paths["/resource"].post.responses['201']
remove: true
- target: $.paths["/resource"].post.responses['202']
remove: true
# Finally, we'll add a catch-all for 2XX responses that uses our discriminators.
- target: $.paths["/resource"].post.responses
update:
'2XX':
description: Success response
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/TypeA'
- $ref: '#/components/schemas/TypeB'
- $ref: '#/components/schemas/TypeC'
discriminator:
propertyName: _type

This overlay:

  1. Creates a ResponseType enum with possible values
  2. Adds an _type field to each model
  3. Replaces specific response codes with a catch-all 2XX response
  4. Sets up a discriminator on the _type property

Creating a hook to inject discriminators

Once you’ve set up your schema, you’ll need to create a hook that injects the discriminator field into the response. This hook will run after a successful API response and before the response is processed by the SDK.

import { isPlainObject } from "../../lib/is-plain-object.js";
import { TypeA$inboundSchema } from "../../models/components/typea.js";
import { TypeB$inboundSchema } from "../../models/components/typeb.js";
import { ResponseType } from "../../models/components/responsetype.js";
import { TypeC$inboundSchema } from "../../models/components/typec.js";
import type { AfterSuccessContext, AfterSuccessHook } from "../types.js";
export default class InjectDiscriminator implements AfterSuccessHook {
/**
* A hook that is called after the SDK receives a response. The hook can
* introduce instrumentation code such as logging, tracing and metrics or
* modify the response before it is handled or throw an error to stop the
* response from being handled.
*/
async afterSuccess(
hookCtx: AfterSuccessContext,
response: Response,
): Promise<Response> {
if (hookCtx.operationID === "createResource") {
return _injectResourceDiscriminator(response);
}
return response;
}
}
export async function _injectResourceDiscriminator(
response: Response,
): Promise<Response> {
// Clone the response to leave the original body untouched
const responseClone = response.clone();
const data: { _type?: ResponseType } = await responseClone.json();
if (!isPlainObject(data)) {
return response;
}
// Use status code and response data to determine the type
if (response.status === 200) {
const isTypeA = TypeA$inboundSchema.safeParse({
...data,
_type: ResponseType.TypeA,
}).success;
if (isTypeA) {
const newData = {
...data,
_type: ResponseType.TypeA,
};
return new Response(JSON.stringify(newData), response);
}
}
if (response.status === 201) {
const isTypeB = TypeB$inboundSchema.safeParse({
...data,
_type: ResponseType.TypeB,
}).success;
if (isTypeB) {
const newData = {
...data,
_type: ResponseType.TypeB,
};
return new Response(JSON.stringify(newData), response);
}
}
if (response.status === 202) {
const isTypeC = TypeC$inboundSchema.safeParse({
...data,
_type: ResponseType.TypeC,
}).success;
if (isTypeC) {
const newData = {
...data,
_type: ResponseType.TypeC,
};
return new Response(JSON.stringify(newData), response);
}
}
// Default case - if we couldn't determine the type, return the original response
return response;
}
Info Icon

Note

The hook shown above is just an example. You’ll need to adapt it to your specific schema and API responses.

Registering the hook

Once you’ve created your hook, you need to register it with the SDK. Follow the instructions in the SDK hooks documentation to register your hook.

For example, in TypeScript:

// In src/hooks/registration.ts
import { Hooks } from "./types";
import InjectDiscriminator from "./injectDiscriminator";
export function initHooks(hooks: Hooks) {
const injectDiscriminator = new InjectDiscriminator();
hooks.registerAfterSuccessHook(injectDiscriminator);
}

Testing the hook

To test your hook:

  1. Make an API request that should return one of your polymorphic types
  2. Check that the response includes the discriminator field
  3. Verify that the SDK correctly deserializes the response into the appropriate type
// Example testing code
const sdk = new SDK();
// This should trigger the hook to add a discriminator
const response = await sdk.resource.create({
name: "Test Resource",
value: 123
});
// The response should be properly typed based on the discriminator
console.log(response.type); // Should be TypeA, TypeB, or TypeC

Conclusion

Using SDK hooks to inject discriminators is a powerful way to handle polymorphic responses in your API. This approach allows you to:

  1. Add discriminator fields to responses that don’t include them
  2. Enable proper typing in your SDK
  3. Handle different response types in a type-safe way

For more information on using SDK hooks, see the SDK hooks documentation.