Skip to content

Commit 7eb3183

Browse files
committed
fix: make guard a transformation instead of validation
1 parent 220ff97 commit 7eb3183

File tree

9 files changed

+119
-180
lines changed

9 files changed

+119
-180
lines changed

library/src/actions/guard/guard.test-d.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,44 +13,39 @@ describe('guard', () => {
1313
describe('should return action object', () => {
1414
test('with no message', () => {
1515
expectTypeOf(guard(isPixelString)).toEqualTypeOf<
16-
GuardAction<string, typeof isPixelString, undefined>
16+
GuardAction<string, PixelString, undefined>
1717
>();
1818
});
1919
test('with string message', () => {
2020
expectTypeOf(
21-
guard<string, typeof isPixelString, 'message'>(isPixelString, 'message')
22-
).toEqualTypeOf<GuardAction<string, typeof isPixelString, 'message'>>();
21+
guard<string, PixelString, 'message'>(isPixelString, 'message')
22+
).toEqualTypeOf<GuardAction<string, PixelString, 'message'>>();
2323
});
2424

2525
test('with function message', () => {
2626
expectTypeOf(
27-
guard<string, typeof isPixelString, () => string>(
28-
isPixelString,
29-
() => 'message'
30-
)
31-
).toEqualTypeOf<
32-
GuardAction<string, typeof isPixelString, () => string>
33-
>();
27+
guard<string, PixelString, () => string>(isPixelString, () => 'message')
28+
).toEqualTypeOf<GuardAction<string, PixelString, () => string>>();
3429
});
3530
});
3631

3732
describe('should infer correct types', () => {
3833
test('of input', () => {
3934
expectTypeOf<
40-
InferInput<GuardAction<string, typeof isPixelString, undefined>>
35+
InferInput<GuardAction<string, PixelString, undefined>>
4136
>().toEqualTypeOf<string>();
4237
});
4338

4439
test('of output', () => {
4540
expectTypeOf<
46-
InferOutput<GuardAction<string, typeof isPixelString, undefined>>
41+
InferOutput<GuardAction<string, PixelString, undefined>>
4742
>().toEqualTypeOf<PixelString>();
4843
});
4944

5045
test('of issue', () => {
5146
expectTypeOf<
52-
InferIssue<GuardAction<string, typeof isPixelString, undefined>>
53-
>().toEqualTypeOf<GuardIssue<string, typeof isPixelString>>();
47+
InferIssue<GuardAction<string, PixelString, undefined>>
48+
>().toEqualTypeOf<GuardIssue<string, PixelString>>();
5449
});
5550
});
5651

library/src/actions/guard/guard.test.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,20 @@ describe('guard', () => {
99
/^\d+px$/u.test(input);
1010

1111
const baseAction: Omit<
12-
GuardAction<string, typeof isPixelString, undefined>,
12+
GuardAction<string, PixelString, undefined>,
1313
'message'
1414
> = {
15-
kind: 'validation',
15+
kind: 'transformation',
1616
type: 'guard',
1717
reference: guard,
18-
expects: null,
1918
requirement: isPixelString,
2019
async: false,
2120
'~run': expect.any(Function),
2221
};
2322

2423
describe('should return action object', () => {
2524
test('with undefined message', () => {
26-
const action: GuardAction<string, typeof isPixelString, undefined> = {
25+
const action: GuardAction<string, PixelString, undefined> = {
2726
...baseAction,
2827
message: undefined,
2928
};
@@ -32,7 +31,7 @@ describe('guard', () => {
3231
});
3332

3433
test('with string message', () => {
35-
const action: GuardAction<string, typeof isPixelString, 'message'> = {
34+
const action: GuardAction<string, PixelString, 'message'> = {
3635
...baseAction,
3736
message: 'message',
3837
};
@@ -41,11 +40,10 @@ describe('guard', () => {
4140

4241
test('with function message', () => {
4342
const message = () => 'message';
44-
const action: GuardAction<string, typeof isPixelString, typeof message> =
45-
{
46-
...baseAction,
47-
message,
48-
};
43+
const action: GuardAction<string, PixelString, typeof message> = {
44+
...baseAction,
45+
message,
46+
};
4947
expect(guard(isPixelString, message)).toStrictEqual(action);
5048
});
5149
});
@@ -61,10 +59,10 @@ describe('guard', () => {
6159
test('should return dataset with issues', () => {
6260
const action = guard(isPixelString, 'message');
6361
const baseIssue: Omit<
64-
GuardIssue<string, typeof isPixelString>,
62+
GuardIssue<string, PixelString>,
6563
'input' | 'received'
6664
> = {
67-
kind: 'validation',
65+
kind: 'transformation',
6866
type: 'guard',
6967
expected: null,
7068
message: 'message',
@@ -86,6 +84,6 @@ describe('guard', () => {
8684
received: '"123"',
8785
},
8886
],
89-
} satisfies FailureDataset<GuardIssue<string, typeof isPixelString>>);
87+
} satisfies FailureDataset<GuardIssue<string, PixelString>>);
9088
});
9189
});

library/src/actions/guard/guard.ts

Lines changed: 38 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
import type {
22
BaseIssue,
3-
BaseValidation,
3+
BaseTransformation,
44
ErrorMessage,
55
} from '../../types/index.ts';
66
import { _addIssue } from '../../utils/index.ts';
77

8-
export type Guard<TInput> = (input: TInput) => input is TInput;
8+
export type Guard<TInput, TOutput extends TInput> = (
9+
input: TInput
10+
) => input is TOutput;
911

10-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11-
export type InferGuarded<TGuard extends Guard<any>> = TGuard extends (
12-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13-
input: any
14-
) => input is infer TGuarded
15-
? TGuarded
16-
: never;
12+
/**
13+
* Guard issue interface.
14+
*/
15+
export interface GuardIssue<TInput, TOutput extends TInput>
16+
extends BaseIssue<TInput> {
17+
/**
18+
* The issue kind.
19+
*/
20+
readonly kind: 'transformation';
21+
/**
22+
* The validation type.
23+
*/
24+
readonly type: 'guard';
25+
/**
26+
* The validation requirement.
27+
*/
28+
readonly requirement: Guard<TInput, TOutput>;
29+
}
1730

1831
/**
1932
* Guard action interface.
2033
*/
2134
export interface GuardAction<
2235
TInput,
23-
TGuard extends Guard<TInput>,
24-
TMessage extends ErrorMessage<GuardIssue<TInput, TGuard>> | undefined,
25-
> extends BaseValidation<
26-
TInput,
27-
InferGuarded<TGuard>,
28-
GuardIssue<TInput, TGuard>
29-
> {
36+
TOutput extends TInput,
37+
TMessage extends ErrorMessage<GuardIssue<TInput, TOutput>> | undefined,
38+
> extends BaseTransformation<TInput, TOutput, GuardIssue<TInput, TOutput>> {
3039
/**
3140
* The action type.
3241
*/
@@ -35,46 +44,25 @@ export interface GuardAction<
3544
* The action reference.
3645
*/
3746
readonly reference: typeof guard;
38-
/**
39-
* The expected property.
40-
*/
41-
readonly expects: null;
4247
/**
4348
* The guard function.
4449
*/
45-
readonly requirement: TGuard;
50+
readonly requirement: Guard<TInput, TOutput>;
4651
/**
4752
* The error message.
4853
*/
4954
readonly message: TMessage;
5055
}
51-
52-
/**
53-
* Guard issue interface.
54-
*/
55-
export interface GuardIssue<TInput, TGuard extends Guard<TInput>>
56-
extends BaseIssue<TInput> {
57-
/**
58-
* The validation type.
59-
*/
60-
type: 'guard';
61-
/**
62-
* The validation requirement.
63-
*/
64-
requirement: TGuard;
65-
}
66-
6756
/**
6857
* Creates a guard validation action.
6958
*
7059
* @param requirement The guard function.
7160
*
7261
* @returns A guard action.
7362
*/
74-
// overload for a known input, i.e. within a pipe
75-
export function guard<TInput, const TGuard extends Guard<TInput>>(
76-
requirement: TGuard
77-
): GuardAction<TInput, TGuard, undefined>;
63+
export function guard<TInput, TOutput extends TInput>(
64+
requirement: Guard<TInput, TOutput>
65+
): GuardAction<TInput, TOutput, undefined>;
7866

7967
/**
8068
* Creates a guard validation action.
@@ -84,64 +72,29 @@ export function guard<TInput, const TGuard extends Guard<TInput>>(
8472
*
8573
* @returns A guard action.
8674
*/
87-
// overload for a known input, i.e. within a pipe
8875
export function guard<
8976
TInput,
90-
const TGuard extends Guard<TInput>,
91-
const TMessage extends ErrorMessage<GuardIssue<TInput, TGuard>> | undefined,
77+
TOutput extends TInput,
78+
const TMessage extends ErrorMessage<GuardIssue<TInput, TOutput>> | undefined,
9279
>(
93-
requirement: TGuard,
80+
requirement: Guard<TInput, TOutput>,
9481
message: TMessage
95-
): GuardAction<TInput, TGuard, TMessage>;
96-
97-
/**
98-
* Creates a guard validation action.
99-
*
100-
* @param requirement The guard function.
101-
*
102-
* @returns A guard action.
103-
*/
104-
// overload for an unknown input, i.e. standalone
105-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
106-
export function guard<const TGuard extends Guard<any>>(
107-
requirement: TGuard
108-
): GuardAction<Parameters<TGuard>[0], TGuard, undefined>;
109-
110-
/**
111-
* Creates a guard validation action.
112-
*
113-
* @param requirement The guard function.
114-
* @param message The error message.
115-
*
116-
* @returns A guard action.
117-
*/
118-
// overload for an unknown input, i.e. standalone
119-
export function guard<
120-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
121-
const TGuard extends Guard<any>,
122-
const TMessage extends
123-
| ErrorMessage<GuardIssue<Parameters<TGuard>[0], TGuard>>
124-
| undefined,
125-
>(
126-
requirement: TGuard,
127-
message: TMessage
128-
): GuardAction<Parameters<TGuard>[0], TGuard, TMessage>;
82+
): GuardAction<TInput, TOutput, TMessage>;
12983

13084
// @__NO_SIDE_EFFECTS__
13185
export function guard(
132-
requirement: Guard<unknown>,
133-
message?: ErrorMessage<GuardIssue<unknown, Guard<unknown>>>
86+
requirement: Guard<unknown, unknown>,
87+
message?: ErrorMessage<GuardIssue<unknown, unknown>>
13488
): GuardAction<
13589
unknown,
136-
Guard<unknown>,
137-
ErrorMessage<GuardIssue<unknown, Guard<unknown>>> | undefined
90+
unknown,
91+
ErrorMessage<GuardIssue<unknown, unknown>> | undefined
13892
> {
13993
return {
140-
kind: 'validation',
94+
kind: 'transformation',
14195
type: 'guard',
14296
reference: guard,
14397
async: false,
144-
expects: null,
14598
requirement,
14699
message,
147100
'~run'(dataset, config) {

website/src/routes/api/(actions)/guard/index.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: guard
3-
description: Creates a guard validation action.
3+
description: Creates a guard transformation action.
44
source: /actions/guard/guard.ts
55
contributors:
66
- EskiMojo14
@@ -11,16 +11,16 @@ import { properties } from './properties';
1111

1212
# guard
1313

14-
Creates a guard validation action.
14+
Creates a guard transformation action.
1515

1616
```ts
17-
const Action = v.guard<TInput, TGuard, TMessage>(requirement, message);
17+
const Action = v.guard<TInput, TOutput, TMessage>(requirement, message);
1818
```
1919

2020
## Generics
2121

2222
- `TInput` <Property {...properties.TInput} />
23-
- `TGuard` <Property {...properties.TGuard} />
23+
- `TOutput` <Property {...properties.TOutput} />
2424
- `TMessage` <Property {...properties.TMessage} />
2525

2626
## Parameters
@@ -34,6 +34,8 @@ With `guard` you can freely validate the input and return `true` if it is valid
3434

3535
This is especially useful if you have an existing type predicate (for example, from an external library).
3636

37+
> `guard` is useful for narrowing known types. For validating completely unknown values, consider [`custom`](../custom/) instead.
38+
3739
## Returns
3840

3941
- `Action` <Property {...properties.Action} />
@@ -49,9 +51,7 @@ Schema to validate a pixel string.
4951
```ts
5052
const PixelStringSchema = v.pipe(
5153
v.string(),
52-
v.guard((input): input is `${number}px` =>
53-
typeof input === 'string' ? /^\d+px$/.test(input) : false
54-
)
54+
v.guard((input): input is `${number}px` => /^\d+px$/.test(input))
5555
);
5656
```
5757

0 commit comments

Comments
 (0)