Skip to content

Commit

Permalink
Merge pull request #1013 from fabian-hiller/feat-exact-optional-prope…
Browse files Browse the repository at this point in the history
…rties-2

Add `exactOptional` and `exactOptionalAsync` schema and change validation of missing object entries in `looseObject`, `looseObjectAsync`, `object`, `objectAsync`, `objectWithRest`, `objectWithRestAsync`, `strictObject` and `strictObject` schema
  • Loading branch information
fabian-hiller authored Jan 22, 2025
2 parents 8f70d30 + d42d890 commit 81301f7
Show file tree
Hide file tree
Showing 244 changed files with 9,646 additions and 1,355 deletions.
2 changes: 2 additions & 0 deletions library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ All notable changes to the library will be documented in this file.
- Add `rfcEmail` action to validate RFC 5322 email addresses (pull request #912)
- Add new overload signature to `pipe` and `pipeAync` method to support unlimited pipe items of same input and output type (issue #852)
- Add `@__NO_SIDE_EFFECTS__` notation to improve tree shaking (pull request #995)
- Add `exactOptional` and `exactOptionalAsync` schema (PR #1013)
- Change types and implementation to support Standard Schema
- Change behaviour of `minValue` and `maxValue` for `NaN` (pull request #843)
- Change type and behaviour of `nullable`, `nullableAsync`, `nullish`, `nullishAsync`, `optional`, `optionalAsync`, `undefinedable` and `undefinedableAsync` for undefined default value (issue #878)
- Change type signature of `partialCheck` and `partialCheckAsync` action to add `.pathList` property in a type-safe way
- Change type signature of `findItem` action to support type predicates (issue #867)
- Change validation of missing object entries in `looseObject`, `looseObjectAsync`, `object`, `objectAsync`, `objectWithRest`, `objectWithRestAsync`, `strictObject` and `strictObject` (PR #1013)
- Refactor `bytes`, `maxBytes`, `minBytes` and `notBytes` action
- Fix implementation of `nonOptional`, `nonOptionalAsync`, `nonNullable`, `nonNullableAsync`, `nonNullish` and `nonNullishAsync` schema in edge cases (issue #909)
- Fix instantiation error for `any` in `PathKeys` type (issue #929)
Expand Down
17 changes: 17 additions & 0 deletions library/src/methods/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ import type { BaseIssue, Config } from '../../types/index.ts';
import { config } from './config.ts';

describe('config', () => {
test('should return copy of passed schema', () => {
expect(config(string(), {})).toStrictEqual({
kind: 'schema',
type: 'string',
reference: string,
expects: 'string',
async: false,
message: undefined,
'~standard': {
version: 1,
vendor: 'valibot',
validate: expect.any(Function),
},
'~run': expect.any(Function),
});
});

test('should override config of schema', () => {
const schema = string();
// @ts-expect-error
Expand Down
5 changes: 3 additions & 2 deletions library/src/methods/fallback/fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
Config,
InferIssue,
InferOutput,
MaybeReadonly,
OutputDataset,
} from '../../types/index.ts';
import { _getStandardProps } from '../../utils/index.ts';
Expand All @@ -15,11 +16,11 @@ import { getFallback } from '../getFallback/index.ts';
export type Fallback<
TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>,
> =
| InferOutput<TSchema>
| MaybeReadonly<InferOutput<TSchema>>
| ((
dataset?: OutputDataset<InferOutput<TSchema>, InferIssue<TSchema>>,
config?: Config<InferIssue<TSchema>>
) => InferOutput<TSchema>);
) => MaybeReadonly<InferOutput<TSchema>>);

/**
* Schema with fallback type.
Expand Down
5 changes: 3 additions & 2 deletions library/src/methods/fallback/fallbackAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
InferIssue,
InferOutput,
MaybePromise,
MaybeReadonly,
OutputDataset,
StandardProps,
UnknownDataset,
Expand All @@ -22,11 +23,11 @@ export type FallbackAsync<
| BaseSchema<unknown, unknown, BaseIssue<unknown>>
| BaseSchemaAsync<unknown, unknown, BaseIssue<unknown>>,
> =
| InferOutput<TSchema>
| MaybeReadonly<InferOutput<TSchema>>
| ((
dataset?: OutputDataset<InferOutput<TSchema>, InferIssue<TSchema>>,
config?: Config<InferIssue<TSchema>>
) => MaybePromise<InferOutput<TSchema>>);
) => MaybePromise<MaybeReadonly<InferOutput<TSchema>>>);

/**
* Schema with fallback async type.
Expand Down
129 changes: 129 additions & 0 deletions library/src/methods/getDefault/getDefault.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { describe, expectTypeOf, test } from 'vitest';
import {
exactOptional,
exactOptionalAsync,
nullable,
nullableAsync,
nullish,
Expand All @@ -9,7 +11,10 @@ import {
optional,
optionalAsync,
string,
undefinedable,
undefinedableAsync,
} from '../../schemas/index.ts';
import { pipe, pipeAsync } from '../pipe/index.ts';
import { getDefault } from './getDefault.ts';

describe('getDefault', () => {
Expand All @@ -19,6 +24,47 @@ describe('getDefault', () => {
expectTypeOf(getDefault(object({}))).toEqualTypeOf<undefined>();
});

describe('should return exact optional default', () => {
test('for undefined value', () => {
expectTypeOf(
getDefault(exactOptional(string()))
).toEqualTypeOf<undefined>();
expectTypeOf(
getDefault(exactOptional(string(), undefined))
).toEqualTypeOf<undefined>();
});

test('for direct value', () => {
expectTypeOf(
getDefault(exactOptional(string(), 'foo'))
).toEqualTypeOf<'foo'>();
});

test('for value getter', () => {
expectTypeOf(
getDefault(exactOptional(string(), () => 'foo' as const))
).toEqualTypeOf<'foo'>();
});

test('for async value getter', () => {
expectTypeOf(
getDefault(exactOptionalAsync(string(), async () => 'foo' as const))
).toEqualTypeOf<Promise<'foo'>>();
});

test('for schema with pipe', () => {
expectTypeOf(
getDefault(pipe(exactOptional(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(exactOptional(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(exactOptionalAsync(string(), 'foo')))
).toEqualTypeOf<'foo'>();
});
});

describe('should return optional default', () => {
test('for undefined value', () => {
expectTypeOf(getDefault(optional(string()))).toEqualTypeOf<undefined>();
Expand Down Expand Up @@ -50,6 +96,18 @@ describe('getDefault', () => {
getDefault(optionalAsync(string(), async () => 'foo' as const))
).toEqualTypeOf<Promise<'foo'>>();
});

test('for schema with pipe', () => {
expectTypeOf(
getDefault(pipe(optional(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(optional(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(optionalAsync(string(), 'foo')))
).toEqualTypeOf<'foo'>();
});
});

describe('should return nullable default', () => {
Expand Down Expand Up @@ -84,6 +142,18 @@ describe('getDefault', () => {
getDefault(nullableAsync(string(), async () => 'foo' as const))
).toEqualTypeOf<Promise<'foo'>>();
});

test('for schema with pipe', () => {
expectTypeOf(
getDefault(pipe(nullable(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(nullable(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(nullableAsync(string(), 'foo')))
).toEqualTypeOf<'foo'>();
});
});

describe('should return nullish default', () => {
Expand Down Expand Up @@ -125,5 +195,64 @@ describe('getDefault', () => {
getDefault(nullishAsync(string(), async () => 'foo' as const))
).toEqualTypeOf<Promise<'foo'>>();
});

test('for schema with pipe', () => {
expectTypeOf(
getDefault(pipe(nullish(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(nullish(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(nullishAsync(string(), 'foo')))
).toEqualTypeOf<'foo'>();
});
});

describe('should return undefinedable default', () => {
test('for undefined value', () => {
expectTypeOf(
getDefault(undefinedable(string()))
).toEqualTypeOf<undefined>();
expectTypeOf(
getDefault(undefinedable(string(), undefined))
).toEqualTypeOf<undefined>();
expectTypeOf(
getDefault(undefinedable(string(), () => undefined))
).toEqualTypeOf<undefined>();
expectTypeOf(
getDefault(undefinedableAsync(string(), async () => undefined))
).toEqualTypeOf<Promise<undefined>>();
});

test('for direct value', () => {
expectTypeOf(
getDefault(undefinedable(string(), 'foo'))
).toEqualTypeOf<'foo'>();
});

test('for value getter', () => {
expectTypeOf(
getDefault(undefinedable(string(), () => 'foo' as const))
).toEqualTypeOf<'foo'>();
});

test('for async value getter', () => {
expectTypeOf(
getDefault(undefinedableAsync(string(), async () => 'foo' as const))
).toEqualTypeOf<Promise<'foo'>>();
});

test('for schema with pipe', () => {
expectTypeOf(
getDefault(pipe(undefinedable(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(undefinedable(string(), 'foo')))
).toEqualTypeOf<'foo'>();
expectTypeOf(
getDefault(pipeAsync(undefinedableAsync(string(), 'foo')))
).toEqualTypeOf<'foo'>();
});
});
});
91 changes: 91 additions & 0 deletions library/src/methods/getDefault/getDefault.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { describe, expect, test } from 'vitest';
import {
exactOptional,
exactOptionalAsync,
nullable,
nullableAsync,
nullish,
Expand All @@ -9,7 +11,10 @@ import {
optional,
optionalAsync,
string,
undefinedable,
undefinedableAsync,
} from '../../schemas/index.ts';
import { pipe, pipeAsync } from '../pipe/index.ts';
import { getDefault } from './getDefault.ts';

describe('getDefault', () => {
Expand All @@ -19,6 +24,37 @@ describe('getDefault', () => {
expect(getDefault(object({}))).toBeUndefined();
});

describe('should return exact optional default', () => {
test('for undefined value', async () => {
expect(getDefault(exactOptional(string()))).toBeUndefined();
expect(getDefault(exactOptional(string(), undefined))).toBeUndefined();
});

test('for direct value', () => {
expect(getDefault(exactOptional(string(), 'foo'))).toBe('foo');
expect(getDefault(exactOptionalAsync(string(), 'foo'))).toBe('foo');
});

test('for value getter', () => {
expect(getDefault(exactOptional(string(), () => 'foo'))).toBe('foo');
expect(getDefault(exactOptionalAsync(string(), () => 'foo'))).toBe('foo');
});

test('for asycn value getter', async () => {
expect(
await getDefault(exactOptionalAsync(string(), async () => 'foo'))
).toBe('foo');
});

test('for schema with pipe', () => {
expect(getDefault(pipe(exactOptional(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(exactOptional(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(exactOptionalAsync(string(), 'foo')))).toBe(
'foo'
);
});
});

describe('should return optional default', () => {
test('for undefined value', async () => {
expect(getDefault(optional(string()))).toBeUndefined();
Expand All @@ -44,6 +80,12 @@ describe('getDefault', () => {
'foo'
);
});

test('for schema with pipe', () => {
expect(getDefault(pipe(optional(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(optional(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(optionalAsync(string(), 'foo')))).toBe('foo');
});
});

describe('should return nullable default', () => {
Expand Down Expand Up @@ -74,6 +116,12 @@ describe('getDefault', () => {
'foo'
);
});

test('for schema with pipe', () => {
expect(getDefault(pipe(nullable(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(nullable(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(nullableAsync(string(), 'foo')))).toBe('foo');
});
});

describe('should return nullish default', () => {
Expand Down Expand Up @@ -109,5 +157,48 @@ describe('getDefault', () => {
'foo'
);
});

test('for schema with pipe', () => {
expect(getDefault(pipe(nullish(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(nullish(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(nullishAsync(string(), 'foo')))).toBe('foo');
});
});

describe('should return undefinedable default', () => {
test('for undefined value', async () => {
expect(getDefault(undefinedable(string()))).toBeUndefined();
expect(getDefault(undefinedable(string(), undefined))).toBeUndefined();
expect(
getDefault(undefinedable(string(), () => undefined))
).toBeUndefined();
expect(
await getDefault(undefinedableAsync(string(), async () => undefined))
).toBeUndefined();
});

test('for direct value', () => {
expect(getDefault(undefinedable(string(), 'foo'))).toBe('foo');
expect(getDefault(undefinedableAsync(string(), 'foo'))).toBe('foo');
});

test('for value getter', () => {
expect(getDefault(undefinedable(string(), () => 'foo'))).toBe('foo');
expect(getDefault(undefinedableAsync(string(), () => 'foo'))).toBe('foo');
});

test('for asycn value getter', async () => {
expect(
await getDefault(undefinedableAsync(string(), async () => 'foo'))
).toBe('foo');
});

test('for schema with pipe', () => {
expect(getDefault(pipe(undefinedable(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(undefinedable(string(), 'foo')))).toBe('foo');
expect(getDefault(pipeAsync(undefinedableAsync(string(), 'foo')))).toBe(
'foo'
);
});
});
});
Loading

0 comments on commit 81301f7

Please sign in to comment.