diff --git a/library/src/schemas/looseObject/looseObject.test.ts b/library/src/schemas/looseObject/looseObject.test.ts index 4004fdfd0..1e6e83dc6 100644 --- a/library/src/schemas/looseObject/looseObject.test.ts +++ b/library/src/schemas/looseObject/looseObject.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssue, expectSchemaIssue } from '../../vitest/index.ts'; import { any } from '../any/index.ts'; @@ -140,6 +141,18 @@ describe('looseObject', () => { ]); }); + test('for missing entries with fallback', () => { + expect( + looseObject({ + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + })['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123 }, + }); + }); + test('for exact optional entry', () => { expectNoSchemaIssue(looseObject({ key: exactOptional(string()) }), [ {}, diff --git a/library/src/schemas/looseObject/looseObject.ts b/library/src/schemas/looseObject/looseObject.ts index 9219e88c3..41a0453d2 100644 --- a/library/src/schemas/looseObject/looseObject.ts +++ b/library/src/schemas/looseObject/looseObject.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseSchema, ErrorMessage, @@ -167,6 +167,12 @@ export function looseObject( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/schemas/looseObject/looseObjectAsync.test.ts b/library/src/schemas/looseObject/looseObjectAsync.test.ts index 27b908fdc..5b8714555 100644 --- a/library/src/schemas/looseObject/looseObjectAsync.test.ts +++ b/library/src/schemas/looseObject/looseObjectAsync.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback, fallbackAsync } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssueAsync, @@ -160,6 +161,21 @@ describe('looseObjectAsync', () => { ); }); + test('for missing entries with fallback', async () => { + expect( + await looseObjectAsync({ + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + key3: fallbackAsync(string(), 'bar'), + key4: fallbackAsync(number(), () => 456), + key5: fallbackAsync(string(), async () => 'baz'), + })['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123, key3: 'bar', key4: 456, key5: 'baz' }, + }); + }); + test('for exact optional entry', async () => { await expectNoSchemaIssueAsync( looseObjectAsync({ key: exactOptional(string()) }), diff --git a/library/src/schemas/looseObject/looseObjectAsync.ts b/library/src/schemas/looseObject/looseObjectAsync.ts index 661e12b81..2da47f1ad 100644 --- a/library/src/schemas/looseObject/looseObjectAsync.ts +++ b/library/src/schemas/looseObject/looseObjectAsync.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseSchemaAsync, ErrorMessage, @@ -189,6 +189,12 @@ export function looseObjectAsync( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = await getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/schemas/object/object.test.ts b/library/src/schemas/object/object.test.ts index dc6eedd9a..8410a8e7b 100644 --- a/library/src/schemas/object/object.test.ts +++ b/library/src/schemas/object/object.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssue, expectSchemaIssue } from '../../vitest/index.ts'; import { any } from '../any/index.ts'; @@ -145,6 +146,18 @@ describe('object', () => { ]); }); + test('for missing entries with fallback', () => { + expect( + object({ + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + })['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123 }, + }); + }); + test('for exact optional entry', () => { expectNoSchemaIssue(object({ key: exactOptional(string()) }), [ {}, diff --git a/library/src/schemas/object/object.ts b/library/src/schemas/object/object.ts index 84a0beecc..94a49fa06 100644 --- a/library/src/schemas/object/object.ts +++ b/library/src/schemas/object/object.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseSchema, ErrorMessage, @@ -170,6 +170,12 @@ export function object( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/schemas/object/objectAsync.test.ts b/library/src/schemas/object/objectAsync.test.ts index 7da2c6abe..6084517c1 100644 --- a/library/src/schemas/object/objectAsync.test.ts +++ b/library/src/schemas/object/objectAsync.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback, fallbackAsync } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssueAsync, @@ -158,6 +159,21 @@ describe('objectAsync', () => { ); }); + test('for missing entries with fallback', async () => { + expect( + await objectAsync({ + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + key3: fallbackAsync(string(), 'bar'), + key4: fallbackAsync(number(), () => 456), + key5: fallbackAsync(string(), async () => 'baz'), + })['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123, key3: 'bar', key4: 456, key5: 'baz' }, + }); + }); + test('for exact optional entry', async () => { await expectNoSchemaIssueAsync( objectAsync({ key: exactOptional(string()) }), diff --git a/library/src/schemas/object/objectAsync.ts b/library/src/schemas/object/objectAsync.ts index 863ef49f3..6d5bd1919 100644 --- a/library/src/schemas/object/objectAsync.ts +++ b/library/src/schemas/object/objectAsync.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseSchemaAsync, ErrorMessage, @@ -192,6 +192,12 @@ export function objectAsync( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = await getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/schemas/objectWithRest/objectWithRest.test.ts b/library/src/schemas/objectWithRest/objectWithRest.test.ts index a378caac8..25354656f 100644 --- a/library/src/schemas/objectWithRest/objectWithRest.test.ts +++ b/library/src/schemas/objectWithRest/objectWithRest.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssue, expectSchemaIssue } from '../../vitest/index.ts'; import { any } from '../any/index.ts'; @@ -153,6 +154,21 @@ describe('objectWithRest', () => { ); }); + test('for missing entries with fallback', () => { + expect( + objectWithRest( + { + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + }, + boolean() + )['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123 }, + }); + }); + test('for exact optional entry', () => { expectNoSchemaIssue( objectWithRest({ key: exactOptional(string()) }, number()), diff --git a/library/src/schemas/objectWithRest/objectWithRest.ts b/library/src/schemas/objectWithRest/objectWithRest.ts index 489a0a277..c1d7ebf0d 100644 --- a/library/src/schemas/objectWithRest/objectWithRest.ts +++ b/library/src/schemas/objectWithRest/objectWithRest.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseIssue, BaseSchema, @@ -190,6 +190,12 @@ export function objectWithRest( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/schemas/objectWithRest/objectWithRestAsync.test.ts b/library/src/schemas/objectWithRest/objectWithRestAsync.test.ts index 0ac0440ba..591e8b6dd 100644 --- a/library/src/schemas/objectWithRest/objectWithRestAsync.test.ts +++ b/library/src/schemas/objectWithRest/objectWithRestAsync.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback, fallbackAsync } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssueAsync, @@ -168,6 +169,24 @@ describe('objectWithRestAsync', () => { ); }); + test('for missing entries with fallback', async () => { + expect( + await objectWithRestAsync( + { + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + key3: fallbackAsync(string(), 'bar'), + key4: fallbackAsync(number(), () => 456), + key5: fallbackAsync(string(), async () => 'baz'), + }, + boolean() + )['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123, key3: 'bar', key4: 456, key5: 'baz' }, + }); + }); + test('for exact optional entry', async () => { await expectNoSchemaIssueAsync( objectWithRestAsync({ key: exactOptional(string()) }, number()), diff --git a/library/src/schemas/objectWithRest/objectWithRestAsync.ts b/library/src/schemas/objectWithRest/objectWithRestAsync.ts index 4c6ba0678..1ede2200d 100644 --- a/library/src/schemas/objectWithRest/objectWithRestAsync.ts +++ b/library/src/schemas/objectWithRest/objectWithRestAsync.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseIssue, BaseSchema, @@ -240,6 +240,12 @@ export function objectWithRestAsync( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = await getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/schemas/strictObject/strictObject.test.ts b/library/src/schemas/strictObject/strictObject.test.ts index 6adb64e47..b0918322a 100644 --- a/library/src/schemas/strictObject/strictObject.test.ts +++ b/library/src/schemas/strictObject/strictObject.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssue, expectSchemaIssue } from '../../vitest/index.ts'; import { any } from '../any/index.ts'; @@ -134,6 +135,18 @@ describe('strictObject', () => { ]); }); + test('for missing entries with fallback', () => { + expect( + strictObject({ + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + })['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123 }, + }); + }); + test('for exact optional entry', () => { expectNoSchemaIssue(strictObject({ key: exactOptional(string()) }), [ {}, diff --git a/library/src/schemas/strictObject/strictObject.ts b/library/src/schemas/strictObject/strictObject.ts index 160c7f30c..c71764578 100644 --- a/library/src/schemas/strictObject/strictObject.ts +++ b/library/src/schemas/strictObject/strictObject.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseSchema, ErrorMessage, @@ -163,6 +163,12 @@ export function strictObject( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/schemas/strictObject/strictObjectAsync.test.ts b/library/src/schemas/strictObject/strictObjectAsync.test.ts index 15a65e207..63b04ba90 100644 --- a/library/src/schemas/strictObject/strictObjectAsync.test.ts +++ b/library/src/schemas/strictObject/strictObjectAsync.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest'; +import { fallback, fallbackAsync } from '../../methods/index.ts'; import type { FailureDataset, InferIssue } from '../../types/index.ts'; import { expectNoSchemaIssueAsync, @@ -153,6 +154,21 @@ describe('strictObjectAsync', () => { ); }); + test('for missing entries with fallback', async () => { + expect( + await strictObjectAsync({ + key1: fallback(string(), 'foo'), + key2: fallback(number(), () => 123), + key3: fallbackAsync(string(), 'bar'), + key4: fallbackAsync(number(), () => 456), + key5: fallbackAsync(string(), async () => 'baz'), + })['~run']({ value: {} }, {}) + ).toStrictEqual({ + typed: true, + value: { key1: 'foo', key2: 123, key3: 'bar', key4: 456, key5: 'baz' }, + }); + }); + test('for exact optional entry', async () => { await expectNoSchemaIssueAsync( strictObjectAsync({ key: exactOptional(string()) }), diff --git a/library/src/schemas/strictObject/strictObjectAsync.ts b/library/src/schemas/strictObject/strictObjectAsync.ts index 1893b0783..fb88dbb14 100644 --- a/library/src/schemas/strictObject/strictObjectAsync.ts +++ b/library/src/schemas/strictObject/strictObjectAsync.ts @@ -1,4 +1,4 @@ -import { getDefault } from '../../methods/index.ts'; +import { getDefault, getFallback } from '../../methods/index.ts'; import type { BaseSchemaAsync, ErrorMessage, @@ -185,6 +185,12 @@ export function strictObjectAsync( // @ts-expect-error dataset.value[key] = valueDataset.value; + // Otherwise, if key is missing but has a fallback, use it + // @ts-expect-error + } else if (valueSchema.fallback !== undefined) { + // @ts-expect-error + dataset.value[key] = await getFallback(valueSchema); + // Otherwise, if key is missing and required, add issue } else if ( valueSchema.type !== 'exact_optional' && diff --git a/library/src/types/object.ts b/library/src/types/object.ts index 6d0a4cc37..00a6d7393 100644 --- a/library/src/types/object.ts +++ b/library/src/types/object.ts @@ -1,5 +1,10 @@ import type { ReadonlyAction } from '../actions/index.ts'; -import type { SchemaWithPipe, SchemaWithPipeAsync } from '../methods/index.ts'; +import type { + SchemaWithFallback, + SchemaWithFallbackAsync, + SchemaWithPipe, + SchemaWithPipeAsync, +} from '../methods/index.ts'; import type { ExactOptionalSchema, ExactOptionalSchemaAsync, @@ -63,6 +68,10 @@ export type OptionalEntrySchemaAsync = export interface ObjectEntries { [key: string]: | BaseSchema> + | SchemaWithFallback< + BaseSchema>, + unknown + > | OptionalEntrySchema; } @@ -73,6 +82,15 @@ export interface ObjectEntriesAsync { [key: string]: | BaseSchema> | BaseSchemaAsync> + | SchemaWithFallback< + BaseSchema>, + unknown + > + | SchemaWithFallbackAsync< + | BaseSchema> + | BaseSchemaAsync>, + unknown + > | OptionalEntrySchema | OptionalEntrySchemaAsync; }