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.0x-speakeasy-jsonpath: rfc9535info:title: example overlayversion: 0.0.0actions:- target: $update:openapi: 3.0.3# First, set up a discriminator field on each model that needs it.- target: $.components.schemasupdate:ResponseType:type: stringenum: [TypeA, TypeB, TypeC]x-speakeasy-include: true- target: $.components.schemas.TypeAupdate:required: ['_type']properties:_type:const: TypeA- target: $.components.schemas.TypeBupdate:required: ['_type']properties:_type:const: TypeB- target: $.components.schemas.TypeCupdate: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.responsesupdate:'2XX':description: Success responsecontent:application/json:schema:oneOf:- $ref: '#/components/schemas/TypeA'- $ref: '#/components/schemas/TypeB'- $ref: '#/components/schemas/TypeC'discriminator:propertyName: _type
This overlay:
- Creates a
ResponseType
enum with possible values - Adds an
_type
field to each model - Replaces specific response codes with a catch-all 2XX response
- 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 untouchedconst responseClone = response.clone();const data: { _type?: ResponseType } = await responseClone.json();if (!isPlainObject(data)) {return response;}// Use status code and response data to determine the typeif (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 responsereturn response;}
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.tsimport { 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:
- Make an API request that should return one of your polymorphic types
- Check that the response includes the discriminator field
- Verify that the SDK correctly deserializes the response into the appropriate type
// Example testing codeconst sdk = new SDK();// This should trigger the hook to add a discriminatorconst response = await sdk.resource.create({name: "Test Resource",value: 123});// The response should be properly typed based on the discriminatorconsole.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:
- Add discriminator fields to responses that don’t include them
- Enable proper typing in your SDK
- Handle different response types in a type-safe way
For more information on using SDK hooks, see the SDK hooks documentation.