Skip to content

Commit

Permalink
Add abortEarly option to abort on first error
Browse files Browse the repository at this point in the history
  • Loading branch information
fabian-hiller committed Jul 30, 2023
1 parent 37ac397 commit 57dc853
Show file tree
Hide file tree
Showing 36 changed files with 492 additions and 33 deletions.
1 change: 1 addition & 0 deletions library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to the library will be documented in this file.
- Add `is` method which can be used as a type guard (pull request #13)
- Throw all validation issues of a pipeline by default (issues #18)
- Add `abortPipeEarly` option to abort pipe on first error (issues #18)
- Add `abortEarly` option to abort on first error

## v0.6.0 (July 30, 2023)

Expand Down
1 change: 1 addition & 0 deletions library/src/error/ValiError/ValiError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type Issue = {
input: any;
path?: PathItem[];
issues?: Issues;
abortEarly?: boolean;
abortPipeEarly?: boolean;
};

Expand Down
2 changes: 1 addition & 1 deletion library/src/methods/is/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function is<TSchema extends BaseSchema>(
input: unknown
): input is Input<TSchema> {
try {
schema.parse(input, { abortPipeEarly: true });
schema.parse(input, { abortEarly: true });
return true;
} catch (error) {
return false;
Expand Down
2 changes: 1 addition & 1 deletion library/src/methods/parse/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { BaseSchema, Output, ParseInfo } from '../../types.ts';
export function parse<TSchema extends BaseSchema>(
schema: TSchema,
input: unknown,
info?: Pick<ParseInfo, 'abortPipeEarly'>
info?: Pick<ParseInfo, 'abortEarly' | 'abortPipeEarly'>
): Output<TSchema> {
return schema.parse(input, info);
}
2 changes: 1 addition & 1 deletion library/src/methods/parse/parseAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
export async function parseAsync<TSchema extends BaseSchema | BaseSchemaAsync>(
schema: TSchema,
input: unknown,
info?: Pick<ParseInfo, 'abortPipeEarly'>
info?: Pick<ParseInfo, 'abortEarly' | 'abortPipeEarly'>
): Promise<Output<TSchema>> {
return schema.parse(input, info);
}
2 changes: 1 addition & 1 deletion library/src/methods/safeParse/safeParse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { BaseSchema, Output, ParseInfo } from '../../types.ts';
export function safeParse<TSchema extends BaseSchema>(
schema: TSchema,
input: unknown,
info?: Pick<ParseInfo, 'abortPipeEarly'>
info?: Pick<ParseInfo, 'abortEarly' | 'abortPipeEarly'>
):
| { success: true; data: Output<TSchema> }
| { success: false; error: ValiError } {
Expand Down
2 changes: 1 addition & 1 deletion library/src/methods/safeParse/safeParseAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function safeParseAsync<
>(
schema: TSchema,
input: unknown,
info?: Pick<ParseInfo, 'abortPipeEarly'>
info?: Pick<ParseInfo, 'abortEarly' | 'abortPipeEarly'>
): Promise<
| { success: true; data: Output<TSchema> }
| { success: false; error: ValiError }
Expand Down
24 changes: 24 additions & 0 deletions library/src/schemas/array/array.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest';
import { type ValiError } from '../../error/index.ts';
import { parse } from '../../methods/index.ts';
import {
maxLength,
Expand Down Expand Up @@ -36,6 +37,29 @@ describe('array', () => {
expect(() => parse(schema, 123)).toThrowError(error);
});

test('should throw every issue', () => {
const schema = array(number());
const input = ['1', 2, '3'];
expect(() => parse(schema, input)).toThrowError();
try {
parse(schema, input);
} catch (error) {
expect((error as ValiError).issues.length).toBe(2);
}
});

test('should throw only first issue', () => {
const schema = array(number());
const input = ['1', 2, '3'];
const info = { abortEarly: true };
expect(() => parse(schema, input, info)).toThrowError();
try {
parse(schema, input, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
}
});

test('should execute pipe', () => {
const lengthError = 'Invalid length';
const contentError = 'Invalid content';
Expand Down
5 changes: 4 additions & 1 deletion library/src/schemas/array/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,11 @@ export function array<TArrayItem extends BaseSchema>(
})
);

// Fill issues in case of an error
// Throw or fill issues in case of an error
} catch (error) {
if (info?.abortEarly) {
throw error;
}
issues.push(...(error as ValiError).issues);
}
});
Expand Down
24 changes: 24 additions & 0 deletions library/src/schemas/array/arrayAsync.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest';
import { type ValiError } from '../../error/index.ts';
import { parseAsync } from '../../methods/index.ts';
import {
maxLength,
Expand Down Expand Up @@ -36,6 +37,29 @@ describe('array', () => {
await expect(parseAsync(schema, 123)).rejects.toThrowError(error);
});

test('should throw every issue', async () => {
const schema = arrayAsync(number());
const input = ['1', 2, '3'];
await expect(parseAsync(schema, input)).rejects.toThrowError();
try {
await parseAsync(schema, input);
} catch (error) {
expect((error as ValiError).issues.length).toBe(2);
}
});

test('should throw only first issue', async () => {
const schema = arrayAsync(number());
const input = ['1', 2, '3'];
const info = { abortEarly: true };
await expect(parseAsync(schema, input, info)).rejects.toThrowError();
try {
await parseAsync(schema, input, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
}
});

test('should execute pipe', async () => {
const lengthError = 'Invalid length';
const contentError = 'Invalid content';
Expand Down
5 changes: 4 additions & 1 deletion library/src/schemas/array/arrayAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,11 @@ export function arrayAsync<TArrayItem extends BaseSchema | BaseSchemaAsync>(
}),
});

// Fill issues in case of an error
// Throw or fill issues in case of an error
} catch (error) {
if (info?.abortEarly) {
throw error;
}
issues.push(...(error as ValiError).issues);
}
})
Expand Down
34 changes: 34 additions & 0 deletions library/src/schemas/map/map.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest';
import { type ValiError } from '../../error/index.ts';
import { parse } from '../../methods/index.ts';
import { maxSize, minSize, size } from '../../validations/index.ts';
import { map } from '../map/index.ts';
Expand Down Expand Up @@ -34,6 +35,39 @@ describe('map', () => {
expect(() => parse(schema, new Set())).toThrowError(error);
});

test('should throw every issue', () => {
const schema = map(number(), string());
const input = new Map().set(1, 1).set(2, '2').set('3', '3');
expect(() => parse(schema, input)).toThrowError();
try {
parse(schema, input);
} catch (error) {
expect((error as ValiError).issues.length).toBe(2);
}
});

test('should throw only first issue', () => {
const schema = map(number(), string());
const info = { abortEarly: true };
const input1 = new Map().set(1, 1).set(2, '2').set('3', '3');
expect(() => parse(schema, input1, info)).toThrowError();
try {
parse(schema, input1, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
expect((error as ValiError).issues[0].origin).toBe('value');
}

const input2 = new Map().set('1', 1).set(2, '2').set('3', '3');
expect(() => parse(schema, input2, info)).toThrowError();
try {
parse(schema, input2, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
expect((error as ValiError).issues[0].origin).toBe('key');
}
});

test('should execute pipe', () => {
const sizeError = 'Invalid size';

Expand Down
10 changes: 8 additions & 2 deletions library/src/schemas/map/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,11 @@ export function map<TMapKey extends BaseSchema, TMapValue extends BaseSchema>(
// further down can be recognized as valid value
outputKey = [key.parse(inputKey, { ...info, origin: 'key', path })];

// Fill issues in case of an error
// Throw or fill issues in case of an error
} catch (error) {
if (info?.abortEarly) {
throw error;
}
issues.push(...(error as ValiError).issues);
}

Expand All @@ -133,8 +136,11 @@ export function map<TMapKey extends BaseSchema, TMapValue extends BaseSchema>(
// further down can be recognized as valid value
outputValue = [value.parse(inputValue, { ...info, path })];

// Fill issues in case of an error
// Throw or fill issues in case of an error
} catch (error) {
if (info?.abortEarly) {
throw error;
}
issues.push(...(error as ValiError).issues);
}

Expand Down
34 changes: 34 additions & 0 deletions library/src/schemas/map/mapAsync.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest';
import { type ValiError } from '../../error/index.ts';
import { parseAsync } from '../../methods/index.ts';
import { mapAsync } from '../map/index.ts';
import { string } from '../string/index.ts';
Expand Down Expand Up @@ -38,6 +39,39 @@ describe('mapAsync', () => {
await expect(parseAsync(schema, new Set())).rejects.toThrowError(error);
});

test('should throw every issue', async () => {
const schema = mapAsync(number(), string());
const input = new Map().set(1, 1).set(2, '2').set('3', '3');
await expect(parseAsync(schema, input)).rejects.toThrowError();
try {
await parseAsync(schema, input);
} catch (error) {
expect((error as ValiError).issues.length).toBe(2);
}
});

test('should throw only first issue', async () => {
const schema = mapAsync(number(), string());
const info = { abortEarly: true };
const input1 = new Map().set(1, 1).set(2, '2').set('3', '3');
await expect(parseAsync(schema, input1, info)).rejects.toThrowError();
try {
await parseAsync(schema, input1, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
expect((error as ValiError).issues[0].origin).toBe('value');
}

const input2 = new Map().set('1', 1).set(2, '2').set('3', '3');
await expect(parseAsync(schema, input2, info)).rejects.toThrowError();
try {
await parseAsync(schema, input2, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
expect((error as ValiError).issues[0].origin).toBe('key');
}
});

test('should execute pipe', async () => {
const sizeError = 'Invalid size';

Expand Down
10 changes: 8 additions & 2 deletions library/src/schemas/map/mapAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,11 @@ export function mapAsync<
await key.parse(inputKey, { ...info, origin: 'key', path }),
] as const;

// Fill issues in case of an error
// Throw or fill issues in case of an error
} catch (error) {
if (info?.abortEarly) {
throw error;
}
issues.push(...(error as ValiError).issues);
}
})(),
Expand All @@ -154,8 +157,11 @@ export function mapAsync<
await value.parse(inputValue, { ...info, path }),
] as const;

// Fill issues in case of an error
// Throw or fill issues in case of an error
} catch (error) {
if (info?.abortEarly) {
throw error;
}
issues.push(...(error as ValiError).issues);
}
})(),
Expand Down
24 changes: 24 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 { type ValiError } from '../../error/index.ts';
import { parse } from '../../methods/index.ts';
import { number } from '../number/index.ts';
import { string } from '../string/index.ts';
Expand All @@ -22,6 +23,29 @@ describe('object', () => {
expect(() => parse(schema, 123)).toThrowError(error);
});

test('should throw every issue', () => {
const schema = object({ 1: number(), 2: number(), 3: number() });
const input = { 1: '1', 2: 2, 3: '3' };
expect(() => parse(schema, input)).toThrowError();
try {
parse(schema, input);
} catch (error) {
expect((error as ValiError).issues.length).toBe(2);
}
});

test('should throw only first issue', () => {
const schema = object({ 1: number(), 2: number(), 3: number() });
const input = { 1: '1', 2: 2, 3: '3' };
const info = { abortEarly: true };
expect(() => parse(schema, input, info)).toThrowError();
try {
parse(schema, input, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
}
});

test('should execute pipe', () => {
const input = { key1: '1', key2: 1 };
const transformInput = () => ({ key1: '2', key2: 2 });
Expand Down
5 changes: 4 additions & 1 deletion library/src/schemas/object/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,11 @@ export function object<TObjectShape extends ObjectShape>(
path: getCurrentPath(info, { schema: 'object', input, key, value }),
});

// Fill issues in case of an error
// Throw or fill issues in case of an error
} catch (error) {
if (info?.abortEarly) {
throw error;
}
issues.push(...(error as ValiError).issues);
}
});
Expand Down
24 changes: 24 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 { type ValiError } from '../../error/index.ts';
import { parseAsync } from '../../methods/index.ts';
import { number } from '../number/index.ts';
import { string, stringAsync } from '../string/index.ts';
Expand All @@ -22,6 +23,29 @@ describe('objectAsync', () => {
await expect(parseAsync(schema, 123)).rejects.toThrowError(error);
});

test('should throw every issue', async () => {
const schema = objectAsync({ 1: number(), 2: number(), 3: number() });
const input = { 1: '1', 2: 2, 3: '3' };
await expect(parseAsync(schema, input)).rejects.toThrowError();
try {
await parseAsync(schema, input);
} catch (error) {
expect((error as ValiError).issues.length).toBe(2);
}
});

test('should throw only first issue', async () => {
const schema = objectAsync({ 1: number(), 2: number(), 3: number() });
const input = { 1: '1', 2: 2, 3: '3' };
const info = { abortEarly: true };
await expect(parseAsync(schema, input, info)).rejects.toThrowError();
try {
await parseAsync(schema, input, info);
} catch (error) {
expect((error as ValiError).issues.length).toBe(1);
}
});

test('should execute pipe', async () => {
const input = { key1: '1', key2: 1 };
const transformInput = () => ({ key1: '2', key2: 2 });
Expand Down
Loading

0 comments on commit 57dc853

Please sign in to comment.