Skip to content

Commit

Permalink
Add rawCheck and rawTransform action #597
Browse files Browse the repository at this point in the history
  • Loading branch information
fabian-hiller committed Jun 14, 2024
1 parent a0ea365 commit ec79160
Show file tree
Hide file tree
Showing 22 changed files with 729 additions and 15 deletions.
1 change: 1 addition & 0 deletions library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to the library will be documented in this file.

## vX.X.X (Month DD, YYYY)

- Add `rawCheck`, `rawCheckAsync`, `rawTransform` and `rawTransformAsync` action (issue #597)
- Change `FlatErrors` type for better developer experience (discussion #640)
- Change `pipe` and `pipeAsync` method to mark output as untyped only when necessary (discussion #613)
- Remove unused `skipPipe` option from `Config` type and refactor library
Expand Down
2 changes: 2 additions & 0 deletions library/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export * from './notLength/index.ts';
export * from './notSize/index.ts';
export * from './notValue/index.ts';
export * from './octal/index.ts';
export * from './rawCheck/index.ts';
export * from './rawTransform/index.ts';
export * from './readonly/index.ts';
export * from './regex/index.ts';
export * from './safeInteger/index.ts';
Expand Down
3 changes: 3 additions & 0 deletions library/src/actions/rawCheck/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './rawCheck.ts';
export * from './rawCheckAsync.ts';
export { RawCheckIssue } from './types.ts';
29 changes: 29 additions & 0 deletions library/src/actions/rawCheck/rawCheck.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expectTypeOf, test } from 'vitest';
import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts';
import { rawCheck, type RawCheckAction } from './rawCheck.ts';
import type { RawCheckIssue } from './types.ts';

describe('rawCheck', () => {
test('should return action object', () => {
expectTypeOf(rawCheck<string>(() => {})).toEqualTypeOf<
RawCheckAction<string>
>();
});

describe('should infer correct types', () => {
type Input = ['foo', 123, true];
type Action = RawCheckAction<Input>;

test('of input', () => {
expectTypeOf<InferInput<Action>>().toEqualTypeOf<Input>();
});

test('of output', () => {
expectTypeOf<InferOutput<Action>>().toEqualTypeOf<Input>();
});

test('of issue', () => {
expectTypeOf<InferIssue<Action>>().toEqualTypeOf<RawCheckIssue<Input>>();
});
});
});
49 changes: 49 additions & 0 deletions library/src/actions/rawCheck/rawCheck.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, test } from 'vitest';
import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts';
import { rawCheck, type RawCheckAction } from './rawCheck.ts';
import type { RawCheckIssue } from './types.ts';

describe('rawCheck', () => {
const action = rawCheck<number>(({ dataset, addIssue }) => {
if (dataset.typed && dataset.value <= 0) {
addIssue({ message: 'message' });
}
});

test('should return action object', () => {
expect(action).toStrictEqual({
kind: 'validation',
type: 'raw_check',
reference: rawCheck,
expects: null,
async: false,
_run: expect.any(Function),
} satisfies RawCheckAction<number>);
});

describe('should return dataset without issues', () => {
test('for untyped inputs', () => {
expect(action._run({ typed: false, value: null }, {})).toStrictEqual({
typed: false,
value: null,
});
});

test('for valid inputs', () => {
expectNoActionIssue(action, [1, 12345, Infinity]);
});
});

describe('should return dataset with issues', () => {
const baseIssue: Omit<RawCheckIssue<number>, 'input' | 'received'> = {
kind: 'validation',
type: 'raw_check',
expected: null,
message: 'message',
};

test('for invalid inputs', () => {
expectActionIssue(action, baseIssue, [0, -1, -12345, -Infinity]);
});
});
});
50 changes: 50 additions & 0 deletions library/src/actions/rawCheck/rawCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { BaseValidation } from '../../types/index.ts';
import { _addIssue } from '../../utils/index.ts';
import type { Context, RawCheckIssue } from './types.ts';

/**
* Raw check action type.
*/
export interface RawCheckAction<TInput>
extends BaseValidation<TInput, TInput, RawCheckIssue<TInput>> {
/**
* The action type.
*/
readonly type: 'raw_check';
/**
* The action reference.
*/
readonly reference: typeof rawCheck;
/**
* The expected property.
*/
readonly expects: null;
}

/**
* Creates a raw check validation action.
*
* @param action The validation action.
*
* @returns A raw check action.
*/
export function rawCheck<TInput>(
action: (context: Context<TInput>) => void
): RawCheckAction<TInput> {
return {
kind: 'validation',
type: 'raw_check',
reference: rawCheck,
async: false,
expects: null,
_run(dataset, config) {
action({
dataset,
config,
addIssue: (info) =>
_addIssue(this, info?.label ?? 'input', dataset, config, info),
});
return dataset;
},
};
}
29 changes: 29 additions & 0 deletions library/src/actions/rawCheck/rawCheckAsync.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expectTypeOf, test } from 'vitest';
import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts';
import { type RawCheckActionAsync, rawCheckAsync } from './rawCheckAsync.ts';
import type { RawCheckIssue } from './types.ts';

describe('rawCheckAsync', () => {
test('should return action object', () => {
expectTypeOf(rawCheckAsync<string>(async () => {})).toEqualTypeOf<
RawCheckActionAsync<string>
>();
});

describe('should infer correct types', () => {
type Input = ['foo', 123, true];
type Action = RawCheckActionAsync<Input>;

test('of input', () => {
expectTypeOf<InferInput<Action>>().toEqualTypeOf<Input>();
});

test('of output', () => {
expectTypeOf<InferOutput<Action>>().toEqualTypeOf<Input>();
});

test('of issue', () => {
expectTypeOf<InferIssue<Action>>().toEqualTypeOf<RawCheckIssue<Input>>();
});
});
});
59 changes: 59 additions & 0 deletions library/src/actions/rawCheck/rawCheckAsync.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, expect, test } from 'vitest';
import {
expectActionIssueAsync,
expectNoActionIssueAsync,
} from '../../vitest/index.ts';
import { type RawCheckActionAsync, rawCheckAsync } from './rawCheckAsync.ts';
import type { RawCheckIssue } from './types.ts';

describe('rawCheckAsync', () => {
const action = rawCheckAsync<number>(async ({ dataset, addIssue }) => {
if (dataset.typed && dataset.value <= 0) {
addIssue({ message: 'message' });
}
});

test('should return action object', () => {
expect(action).toStrictEqual({
kind: 'validation',
type: 'raw_check',
reference: rawCheckAsync,
expects: null,
async: true,
_run: expect.any(Function),
} satisfies RawCheckActionAsync<number>);
});

describe('should return dataset without issues', () => {
test('for untyped inputs', async () => {
expect(
await action._run({ typed: false, value: null }, {})
).toStrictEqual({
typed: false,
value: null,
});
});

test('for valid inputs', async () => {
await expectNoActionIssueAsync(action, [1, 12345, Infinity]);
});
});

describe('should return dataset with issues', () => {
const baseIssue: Omit<RawCheckIssue<number>, 'input' | 'received'> = {
kind: 'validation',
type: 'raw_check',
expected: null,
message: 'message',
};

test('for invalid inputs', async () => {
await expectActionIssueAsync(action, baseIssue, [
0,
-1,
-12345,
-Infinity,
]);
});
});
});
50 changes: 50 additions & 0 deletions library/src/actions/rawCheck/rawCheckAsync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { BaseValidationAsync, MaybePromise } from '../../types/index.ts';
import { _addIssue } from '../../utils/index.ts';
import type { Context, RawCheckIssue } from './types.ts';

/**
* Raw check action async type.
*/
export interface RawCheckActionAsync<TInput>
extends BaseValidationAsync<TInput, TInput, RawCheckIssue<TInput>> {
/**
* The action type.
*/
readonly type: 'raw_check';
/**
* The action reference.
*/
readonly reference: typeof rawCheckAsync;
/**
* The expected property.
*/
readonly expects: null;
}

/**
* Creates a raw check validation action.
*
* @param action The validation action.
*
* @returns A raw check action.
*/
export function rawCheckAsync<TInput>(
action: (context: Context<TInput>) => MaybePromise<void>
): RawCheckActionAsync<TInput> {
return {
kind: 'validation',
type: 'raw_check',
reference: rawCheckAsync,
async: true,
expects: null,
async _run(dataset, config) {
await action({
dataset,
config,
addIssue: (info) =>
_addIssue(this, info?.label ?? 'input', dataset, config, info),
});
return dataset;
},
};
}
47 changes: 47 additions & 0 deletions library/src/actions/rawCheck/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type {
BaseIssue,
Config,
Dataset,
ErrorMessage,
IssuePathItem,
} from '../../types/index.ts';

/**
* Raw check issue type.
*/
export interface RawCheckIssue<TInput> extends BaseIssue<TInput> {
/**
* The issue kind.
*/
readonly kind: 'validation';
/**
* The issue type.
*/
readonly type: 'raw_check';
}

/**
* Issue info type.
*/
interface IssueInfo<TInput> {
label?: string;
input?: unknown;
expected?: string;
received?: string;
message?: ErrorMessage<RawCheckIssue<TInput>>;
path?: [IssuePathItem, ...IssuePathItem[]];
}

/**
* Add issue type.
*/
type AddIssue<TInput> = (info?: IssueInfo<TInput>) => void;

/**
* Context type.
*/
export interface Context<TInput> {
readonly dataset: Dataset<TInput, BaseIssue<unknown>>;
readonly config: Config<RawCheckIssue<TInput>>;
readonly addIssue: AddIssue<TInput>;
}
3 changes: 3 additions & 0 deletions library/src/actions/rawTransform/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './rawTransform.ts';
export * from './rawTransformAsync.ts';
export { RawTransformIssue } from './types.ts';
30 changes: 30 additions & 0 deletions library/src/actions/rawTransform/rawTransform.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expectTypeOf, test } from 'vitest';
import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts';
import { rawTransform, type RawTransformAction } from './rawTransform.ts';
import type { RawTransformIssue } from './types.ts';

describe('rawTransform', () => {
test('should return action object', () => {
expectTypeOf(
rawTransform<string, number>(({ dataset }) => dataset.value.length)
).toEqualTypeOf<RawTransformAction<string, number>>();
});

describe('should infer correct types', () => {
type Action = RawTransformAction<string, number>;

test('of input', () => {
expectTypeOf<InferInput<Action>>().toEqualTypeOf<string>();
});

test('of output', () => {
expectTypeOf<InferOutput<Action>>().toEqualTypeOf<number>();
});

test('of issue', () => {
expectTypeOf<InferIssue<Action>>().toEqualTypeOf<
RawTransformIssue<string>
>();
});
});
});
Loading

0 comments on commit ec79160

Please sign in to comment.