Repository: samchungy/zod-openapi
Pull request #331 (2024-10-07 01:25:42)

Add `unionOneOf` global override


Patch:
diff --git a/README.md b/README.md
index 7377f4c..ead93c3 100644
--- a/README.md
+++ b/README.md
@@ -59,16 +59,16 @@ z.string().openapi({ description: 'hello world!', example: 'hello world' });
 
 Use the `.openapi()` method to add metadata to a specific Zod type. The `.openapi()` method takes an object with the following options:
 
-|     Option      |                                                         Description                                                         |
-| :-------------: | :-------------------------------------------------------------------------------------------------------------------------: |
-| OpenAPI Options | This will take any option you would put on a [SchemaObject](https://swagger.io/docs/specification/data-models/data-types/). |
-|  `effectType`   |                             Use to override the creation type for a [Zod Effect](#zod-effects)                              |
-|    `header`     |                              Use to provide metadata for [response headers](#response-headers)                              |
-|     `param`     |                                Use to provide metadata for [request parameters](#parameters)                                |
-|      `ref`      |                     Use this to [auto register a schema as a re-usable component](#creating-components)                     |
-|    `refType`    |                 Use this to set the creation type for a component which is not referenced in the document.                  |
-|     `type`      |                 Use this to override the generated type. If this is provided no metadata will be generated.                 |
-|  `unionOneOf`   |                           Set to `true` to force a ZodUnion to output `oneOf` instead of `allOf`                            |
+|     Option      |                                                                      Description                                                                       |
+| :-------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------: |
+| OpenAPI Options |              This will take any option you would put on a [SchemaObject](https://swagger.io/docs/specification/data-models/data-types/).               |
+|  `effectType`   |                                           Use to override the creation type for a [Zod Effect](#zod-effects)                                           |
+|    `header`     |                                           Use to provide metadata for [response headers](#response-headers)                                            |
+|     `param`     |                                             Use to provide metadata for [request parameters](#parameters)                                              |
+|      `ref`      |                                  Use this to [auto register a schema as a re-usable component](#creating-components)                                   |
+|    `refType`    |                               Use this to set the creation type for a component which is not referenced in the document.                               |
+|     `type`      |                              Use this to override the generated type. If this is provided no metadata will be generated.                               |
+|  `unionOneOf`   | Set to `true` to force a single ZodUnion to output `oneOf` instead of `allOf`. See [CreateDocumentOptions](#CreateDocumentOptions) for a global option |
 
 ### `createDocument`
 
@@ -198,11 +198,14 @@ const document = createDocument({
   ```
 </details>
 
+#### CreateDocumentOptions
+
 `createDocument` takes an optional `CreateDocumentOptions` argument which can be used to modify how the document is created.
 
 ```typescript
 const document = createDocument(details, {
   defaultDateSchema: { type: 'string', format: 'date-time' }, // defaults to { type: 'string' }
+  unionOneOf: true, // defaults to false. Forces all ZodUnions to output `true`
 });
 ```
 
diff --git a/src/create/document.ts b/src/create/document.ts
index 8ffe237..93f1fe4 100644
--- a/src/create/document.ts
+++ b/src/create/document.ts
@@ -155,7 +155,14 @@ export type ZodObjectInputType<
 > = ZodType<Output, Def, Input>;
 
 export interface CreateDocumentOptions {
+  /**
+   * Used to change the default Zod Date schema
+   */
   defaultDateSchema?: Pick<oas31.SchemaObject, 'type' | 'format'>;
+  /**
+   * Used to set the output of a ZodUnion to be `oneOf` instead of `allOf`
+   */
+  unionOneOf?: boolean;
 }
 
 export const createDocument = (
diff --git a/src/create/schema/parsers/union.ts b/src/create/schema/parsers/union.ts
index 4a78062..af92a4c 100644
--- a/src/create/schema/parsers/union.ts
+++ b/src/create/schema/parsers/union.ts
@@ -18,7 +18,7 @@ export const createUnionSchema = <
     createSchemaObject(option, state, [`union option ${index}`]),
   );
 
-  if (zodUnion._def.openapi?.unionOneOf) {
+  if (zodUnion._def.openapi?.unionOneOf ?? state.documentOptions?.unionOneOf) {
     return {
       type: 'schema',
       schema: {


Test patch:
diff --git a/src/create/schema/parsers/union.test.ts b/src/create/schema/parsers/union.test.ts
index 7faf604..a7ba166 100644
--- a/src/create/schema/parsers/union.test.ts
+++ b/src/create/schema/parsers/union.test.ts
@@ -50,4 +50,54 @@ describe('createUnionSchema', () => {
 
     expect(result).toEqual(expected);
   });
+
+  it('creates an oneOf schema for a union if the document options unionOneOf is true', () => {
+    const expected: Schema = {
+      type: 'schema',
+      schema: {
+        oneOf: [
+          {
+            type: 'string',
+          },
+          {
+            type: 'number',
+          },
+        ],
+      },
+    };
+    const schema = z.union([z.string(), z.number()]);
+
+    const result = createUnionSchema(
+      schema,
+      createOutputState(undefined, { unionOneOf: true }),
+    );
+
+    expect(result).toEqual(expected);
+  });
+
+  it('preferences individual unionOneOf over global setting', () => {
+    const expected: Schema = {
+      type: 'schema',
+      schema: {
+        anyOf: [
+          {
+            type: 'string',
+          },
+          {
+            type: 'number',
+          },
+        ],
+      },
+    };
+    const schema = z
+      .union([z.string(), z.number()])
+      .openapi({ unionOneOf: false });
+
+    const result = createUnionSchema(
+      schema,
+      createOutputState(undefined, { unionOneOf: true }),
+    );
+
+    expect(result).toEqual(expected);
+  });
 });