Skip to content

Commit

Permalink
Fix Nullable Behaviour (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
samchungy authored Nov 8, 2023
1 parent cf4fcb1 commit 46f4a25
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 45 deletions.
3 changes: 2 additions & 1 deletion src/create/schema/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { isReferenceObject } from '../../openapi';
import type { oas31 } from '../../openapi3-ts/dist';

export const enhanceWithMetadata = (
schemaObject: oas31.SchemaObject | oas31.ReferenceObject,
metadata: oas31.SchemaObject | oas31.ReferenceObject,
): oas31.SchemaObject | oas31.ReferenceObject => {
if ('$ref' in schemaObject) {
if (isReferenceObject(schemaObject)) {
if (Object.values(metadata).every((val) => val === undefined)) {
return schemaObject;
}
Expand Down
30 changes: 9 additions & 21 deletions src/create/schema/parsers/nullable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,10 @@ describe('createNullableSchema', () => {
expect(result).toStrictEqual(expected);
});

it('creates an oneOf nullable schema for registered schemas', () => {
it('creates an allOf nullable schema for registered schemas', () => {
const expected: oas30.SchemaObject = {
oneOf: [
{
$ref: '#/components/schemas/a',
},
{
nullable: true,
},
],
allOf: [{ $ref: '#/components/schemas/a' }],
nullable: true,
};
const registered = z.string().openapi({ ref: 'a' });
const schema = registered.optional().nullable();
Expand Down Expand Up @@ -65,10 +59,8 @@ describe('createNullableSchema', () => {
},
required: ['b'],
},
{
nullable: true,
},
],
nullable: true,
};
const schema = z
.union([z.object({ a: z.string() }), z.object({ b: z.string() })])
Expand All @@ -84,15 +76,11 @@ describe('createNullableSchema', () => {
type: 'object',
properties: {
b: {
oneOf: [
{
allOf: [{ $ref: '#/components/schemas/a' }],
type: 'object',
properties: { b: { type: 'string' } },
required: ['b'],
},
{ nullable: true },
],
allOf: [{ $ref: '#/components/schemas/a' }],
type: 'object',
properties: { b: { type: 'string' } },
required: ['b'],
nullable: true,
},
},
required: ['b'],
Expand Down
55 changes: 32 additions & 23 deletions src/create/schema/parsers/nullable.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,60 @@
import type { ZodNullable, ZodTypeAny } from 'zod';

import { satisfiesVersion } from '../../../openapi';
import { isReferenceObject, satisfiesVersion } from '../../../openapi';
import type { oas31 } from '../../../openapi3-ts/dist';
import type { ZodOpenApiVersion } from '../../document';
import { type SchemaState, createSchemaObject } from '../../schema';

export const createNullableSchema = <T extends ZodTypeAny>(
zodNullable: ZodNullable<T>,
state: SchemaState,
): oas31.SchemaObject => {
): oas31.SchemaObject | oas31.ReferenceObject => {
const schemaObject = createSchemaObject(zodNullable.unwrap(), state, [
'nullable',
]);

if ('$ref' in schemaObject || schemaObject.allOf) {
return {
oneOf: mapNullOf([schemaObject], state.components.openapi),
};
}
if (satisfiesVersion(state.components.openapi, '3.1.0')) {
if (isReferenceObject(schemaObject) || schemaObject.allOf) {
return {
oneOf: mapNullOf([schemaObject], state.components.openapi),
};
}

if (schemaObject.oneOf) {
const { oneOf, ...schema } = schemaObject;
return {
oneOf: mapNullOf(oneOf, state.components.openapi),
...schema,
};
}

if (schemaObject.anyOf) {
const { anyOf, ...schema } = schemaObject;
return {
anyOf: mapNullOf(anyOf, state.components.openapi),
...schema,
};
}

const { type, ...schema } = schemaObject;

if (schemaObject.oneOf) {
const { oneOf, ...schema } = schemaObject;
return {
oneOf: mapNullOf(oneOf, state.components.openapi),
type: mapNullType(type),
...schema,
};
}

if (schemaObject.anyOf) {
const { anyOf, ...schema } = schemaObject;
if (isReferenceObject(schemaObject)) {
return {
anyOf: mapNullOf(anyOf, state.components.openapi),
...schema,
};
allOf: [schemaObject],
nullable: true,
} as oas31.SchemaObject;
}

const { type, ...schema } = schemaObject;

if (satisfiesVersion(state.components.openapi, '3.1.0')) {
return {
type: mapNullType(type),
...schema,
};
}

return {
type,
...(type && { type }),
nullable: true,
...schema,
// https://github.com/OAI/OpenAPI-Specification/blob/main/proposals/2019-10-31-Clarify-Nullable.md#if-a-schema-specifies-nullable-true-and-enum-1-2-3-does-that-schema-allow-null-values-see-1900
Expand Down
7 changes: 7 additions & 0 deletions src/openapi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { oas31 } from './openapi3-ts/dist';

export const openApiVersions = [
'3.0.0',
'3.0.1',
Expand All @@ -12,3 +14,8 @@ export const satisfiesVersion = (
test: OpenApiVersion,
against: OpenApiVersion,
) => openApiVersions.indexOf(test) >= openApiVersions.indexOf(against);

export const isReferenceObject = (
schemaOrRef: oas31.SchemaObject | oas31.ReferenceObject,
): schemaOrRef is oas31.ReferenceObject =>
Boolean('$ref' in schemaOrRef && schemaOrRef.$ref);

0 comments on commit 46f4a25

Please sign in to comment.