Skip to content

Commit

Permalink
Merge pull request #1031 from fabian-hiller/feat-support-fallback-in-…
Browse files Browse the repository at this point in the history
…objects

Feat: Add native support for `fallback` values in object schemas
  • Loading branch information
fabian-hiller authored Feb 4, 2025
2 parents ba54a51 + 701555e commit e1e9f0e
Show file tree
Hide file tree
Showing 17 changed files with 197 additions and 9 deletions.
13 changes: 13 additions & 0 deletions library/src/schemas/looseObject/looseObject.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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()) }), [
{},
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/looseObject/looseObject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseSchema,
ErrorMessage,
Expand Down Expand Up @@ -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' &&
Expand Down
16 changes: 16 additions & 0 deletions library/src/schemas/looseObject/looseObjectAsync.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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()) }),
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/looseObject/looseObjectAsync.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseSchemaAsync,
ErrorMessage,
Expand Down Expand Up @@ -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' &&
Expand Down
13 changes: 13 additions & 0 deletions library/src/schemas/object/object.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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()) }), [
{},
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/object/object.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseSchema,
ErrorMessage,
Expand Down Expand Up @@ -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' &&
Expand Down
16 changes: 16 additions & 0 deletions library/src/schemas/object/objectAsync.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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()) }),
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/object/objectAsync.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseSchemaAsync,
ErrorMessage,
Expand Down Expand Up @@ -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' &&
Expand Down
16 changes: 16 additions & 0 deletions library/src/schemas/objectWithRest/objectWithRest.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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()),
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/objectWithRest/objectWithRest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseIssue,
BaseSchema,
Expand Down Expand Up @@ -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' &&
Expand Down
19 changes: 19 additions & 0 deletions library/src/schemas/objectWithRest/objectWithRestAsync.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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()),
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/objectWithRest/objectWithRestAsync.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseIssue,
BaseSchema,
Expand Down Expand Up @@ -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' &&
Expand Down
13 changes: 13 additions & 0 deletions library/src/schemas/strictObject/strictObject.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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()) }), [
{},
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/strictObject/strictObject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseSchema,
ErrorMessage,
Expand Down Expand Up @@ -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' &&
Expand Down
16 changes: 16 additions & 0 deletions library/src/schemas/strictObject/strictObjectAsync.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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()) }),
Expand Down
8 changes: 7 additions & 1 deletion library/src/schemas/strictObject/strictObjectAsync.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDefault } from '../../methods/index.ts';
import { getDefault, getFallback } from '../../methods/index.ts';
import type {
BaseSchemaAsync,
ErrorMessage,
Expand Down Expand Up @@ -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' &&
Expand Down
Loading

0 comments on commit e1e9f0e

Please sign in to comment.