Skip to content

Commit

Permalink
Default values are now applied properly for single-type unions, even …
Browse files Browse the repository at this point in the history
…a mix of integers and numbers.
  • Loading branch information
ciscoheat committed Feb 12, 2024
1 parent cfe7d88 commit 409289e
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ Headlines: Added, Changed, Deprecated, Removed, Fixed, Security
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- Default values are now applied properly for single-type unions, even a mix of integers and numbers.

## [2.1.0] - 2024-02-12

### Fixed
Expand Down
32 changes: 22 additions & 10 deletions src/lib/jsonSchema/schemaDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ function _defaultValues(schema: JSONSchema, isOptional: boolean, path: string[])
}
}

let _multiType: Set<string>;
const isMultiTypeUnion = () => {
if (!info.union || info.union.length < 2) return false;
if (info.union.some((i) => i.enum)) return true;
console.log(info.union, info.types, _multiType); //debug
if (!_multiType) {
_multiType = new Set(
info.types.map((i) => {
return ['integer', 'unix-time'].includes(i) ? 'number' : i;
})
);
}
return _multiType.size > 1;
};

// Check unions first, so default values can take precedence over nullable and optional
if (!objectDefaults && info.union) {
const singleDefault = info.union.filter(
Expand All @@ -65,20 +80,17 @@ function _defaultValues(schema: JSONSchema, isOptional: boolean, path: string[])
'Only one default value can exist in a union, or set a default value for the whole union.',
path
);
} else if (info.union.length > 1) {
throw new SchemaError(
'Unions must have a default value, or exactly one of the union types must have.',
path
);
} else {
// Null takes priority over undefined
if (info.isNullable) return null;
if (info.isOptional) return undefined;

throw new SchemaError(
'Unions must have a default value, or exactly one of the union types must have.',
path
);
if (isMultiTypeUnion()) {
throw new SchemaError(
'Multi-type unions must have a default value, or exactly one of the union types must have.',
path
);
}
}
}

Expand Down Expand Up @@ -114,7 +126,7 @@ function _defaultValues(schema: JSONSchema, isOptional: boolean, path: string[])
}

// Basic type
if (info.types.length > 1) {
if (isMultiTypeUnion()) {
throw new SchemaError('Default values cannot have more than one type.', path);
} else if (info.types.length == 0) {
//console.warn('No type or format for property:', path); //debug
Expand Down
22 changes: 22 additions & 0 deletions src/tests/superValidate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,28 @@ describe('Zod', () => {
});
});

it('should not require a default value for single type unions', () => {
const schema = z.object({
letter: z.union([z.literal('a'), z.literal('b')]),
num: z.union([z.number().int().negative(), z.number()])
});

const adapter = zod(schema);
expect(adapter.defaults).toEqual({ letter: 'a', num: 0 });
expect(adapter.constraints.letter?.required).toBe(true);
expect(adapter.constraints.num?.required).toBe(true);
});

it('should not require a default value for enums', () => {
const schema = z.object({
letter: z.enum(['a', 'b', 'c'])
});

const adapter = zod(schema);
expect(adapter.defaults.letter).toBe('a');
expect(adapter.constraints.letter?.required).toBe(true);
});

schemaTest(zod(schema));
});

Expand Down

0 comments on commit 409289e

Please sign in to comment.