Skip to content

Commit 298ba83

Browse files
authored
Convert null types to nullable for better errors (#10)
1 parent ebf491a commit 298ba83

2 files changed

Lines changed: 32 additions & 41 deletions

File tree

src/compileValueSchema.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ function createSafeLiteral(
3535
/**
3636
* Compile a JSON schema into a validation function.
3737
*/
38-
export function compileValueSchema(compiler: Compiler, schema: OpenAPIValueSchema) {
38+
export function compileValueSchema(
39+
compiler: Compiler,
40+
schema: OpenAPIValueSchema,
41+
): namedTypes.Identifier {
3942
if ('$ref' in schema) {
4043
return compileValueSchema(compiler, compiler.resolveRef(schema));
4144
}
@@ -108,13 +111,29 @@ function compileNullTypeSchema(compiler: Compiler, schema: object) {
108111

109112
/**
110113
* OpenAPI 3.1: type as array, e.g. type: ['string', 'null']
111-
* Converts to an anyOf schema internally.
114+
* Converts nullable two-type unions to the existing nullable fast-path.
115+
* Other unions are still compiled as anyOf.
112116
*/
113-
function compileTypeArraySchema(compiler: Compiler, schema: OpenAPITypeArraySchema) {
117+
function compileTypeArraySchema(
118+
compiler: Compiler,
119+
schema: OpenAPITypeArraySchema,
120+
): namedTypes.Identifier {
114121
const { type, ...rest } = schema;
115122

116-
const typesWithoutNull = type.filter((t) => t !== 'null');
117-
const hasNull = type.includes('null');
123+
type OpenAPINonNullType = Exclude<OpenAPITypeArraySchema['type'][number], 'null'>;
124+
125+
const typesWithoutNull = type.filter((t): t is OpenAPINonNullType => t !== 'null');
126+
const hasNull = typesWithoutNull.length !== type.length;
127+
128+
if (hasNull && type.length === 2 && typesWithoutNull.length === 1) {
129+
const [nonNullType] = typesWithoutNull;
130+
131+
return compileValueSchema(compiler, {
132+
...rest,
133+
type: nonNullType,
134+
nullable: true,
135+
} as OpenAPIValueSchema);
136+
}
118137

119138
const anyOf: OpenAPIValueSchema[] = typesWithoutNull.map(
120139
(t) => ({ ...rest, type: t }) as OpenAPIValueSchema,

src/tests/__snapshots__/compileValueSchema.test.ts.snap

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,28 +1889,14 @@ export class ValidationError extends RequestError {
18891889
this.path = path;
18901890
}
18911891
}
1892-
function obj1(path, value, context) {
1892+
function obj0(path, value, context) {
1893+
if (value === null) {
1894+
return value;
1895+
}
18931896
if (typeof value !== 'string') {
18941897
return new ValidationError(path, 'expected a string');
18951898
}
18961899
return value;
1897-
}
1898-
function obj2(path, value, context) {
1899-
if (value !== null) {
1900-
return new ValidationError(path, 'expected null');
1901-
}
1902-
return value;
1903-
}
1904-
function obj0(path, value, context) {
1905-
const value0 = obj1(path, value, context);
1906-
if (!(value0 instanceof ValidationError)) {
1907-
return value0;
1908-
}
1909-
const value1 = obj2(path, value, context);
1910-
if (!(value1 instanceof ValidationError)) {
1911-
return value1;
1912-
}
1913-
return new ValidationError(path, 'expected one of the anyOf schemas to match');
19141900
}"
19151901
`;
19161902
@@ -2008,30 +1994,16 @@ export class ValidationError extends RequestError {
20081994
this.path = path;
20091995
}
20101996
}
2011-
function obj1(path, value, context) {
1997+
function obj0(path, value, context) {
1998+
if (value === null) {
1999+
return value;
2000+
}
20122001
if (typeof value !== 'string') {
20132002
return new ValidationError(path, 'expected a string');
20142003
}
20152004
if (value.length < 1) {
20162005
return new ValidationError(path, 'expected at least 1 characters');
20172006
}
20182007
return value;
2019-
}
2020-
function obj2(path, value, context) {
2021-
if (value !== null) {
2022-
return new ValidationError(path, 'expected null');
2023-
}
2024-
return value;
2025-
}
2026-
function obj0(path, value, context) {
2027-
const value0 = obj1(path, value, context);
2028-
if (!(value0 instanceof ValidationError)) {
2029-
return value0;
2030-
}
2031-
const value1 = obj2(path, value, context);
2032-
if (!(value1 instanceof ValidationError)) {
2033-
return value1;
2034-
}
2035-
return new ValidationError(path, 'expected one of the anyOf schemas to match');
20362008
}"
20372009
`;

0 commit comments

Comments
 (0)