Repository: samchungy/zod-openapi
Pull request #369 (2024-11-25 01:53:35)

Add enforceDiscriminatedUnionComponents
https://github.com/samchungy/zod-openapi/discussions/364


Patch:
diff --git a/README.md b/README.md
index d265caa..cabaa97 100644
--- a/README.md
+++ b/README.md
@@ -206,6 +206,7 @@ const document = createDocument({
 const document = createDocument(details, {
   defaultDateSchema: { type: 'string', format: 'date-time' }, // defaults to { type: 'string' }
   unionOneOf: true, // defaults to false. Forces all ZodUnions to output oneOf instead of anyOf. An `.openapi()` `unionOneOf` value takes precedence over this one.
+  enforceDiscriminatedUnionComponents: true, // defaults to false. Throws an error if a Discriminated Union member is not registered as a component.
 });
 ```
 
diff --git a/src/create/document.ts b/src/create/document.ts
index 33fbafc..d316815 100644
--- a/src/create/document.ts
+++ b/src/create/document.ts
@@ -155,6 +155,10 @@ export type ZodObjectInputType<
 > = ZodType<Output, Def, Input>;
 
 export interface CreateDocumentOptions {
+  /**
+   * Used to throw an error if a Discriminated Union member is not registered as a component
+   */
+  enforceDiscriminatedUnionComponents?: boolean;
   /**
    * Used to change the default Zod Date schema
    */
diff --git a/src/create/schema/parsers/discriminatedUnion.ts b/src/create/schema/parsers/discriminatedUnion.ts
index 6861dfc..1f928ba 100644
--- a/src/create/schema/parsers/discriminatedUnion.ts
+++ b/src/create/schema/parsers/discriminatedUnion.ts
@@ -36,6 +36,7 @@ export const createDiscriminatedUnionSchema = <
     zodDiscriminatedUnion.discriminator,
     state,
   );
+
   return {
     type: 'schema',
     schema: {
@@ -98,6 +99,11 @@ export const mapDiscriminator = (
     const schema = schemas[index] as oas31.SchemaObject | oas31.ReferenceObject;
     const componentSchemaRef = '$ref' in schema ? schema?.$ref : undefined;
     if (!componentSchemaRef) {
+      if (state.documentOptions?.enforceDiscriminatedUnionComponents) {
+        throw new Error(
+          `Discriminated Union member ${index} at ${state.path.join(' > ')} is not registered as a component`,
+        );
+      }
       return undefined;
     }
 


Test patch:
diff --git a/src/create/schema/parsers/discriminatedUnion.test.ts b/src/create/schema/parsers/discriminatedUnion.test.ts
index 7c77470..69c2f7f 100644
--- a/src/create/schema/parsers/discriminatedUnion.test.ts
+++ b/src/create/schema/parsers/discriminatedUnion.test.ts
@@ -486,4 +486,26 @@ describe('createDiscriminatedUnionSchema', () => {
       ],
     });
   });
+
+  it('throws an error if enforceDiscriminatedUnionComponents is specified and a schema is not registered', () => {
+    const schema = z.discriminatedUnion('type', [
+      z.object({
+        type: z.literal('a'),
+      }),
+      z
+        .object({
+          type: z.literal('b'),
+        })
+        .openapi({ ref: 'b' }),
+    ]);
+
+    expect(() =>
+      createDiscriminatedUnionSchema(schema, {
+        ...createOutputState({}, { enforceDiscriminatedUnionComponents: true }),
+        path: ['some', 'path'],
+      }),
+    ).toThrowErrorMatchingInlineSnapshot(
+      `"Discriminated Union member 0 at some > path is not registered as a component"`,
+    );
+  });
 });