@strictly/define 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.out/index.d.ts +2 -0
- package/.out/index.js +2 -0
- package/.out/transformers/flatteners/flatten_type_to.d.ts +1 -1
- package/.out/transformers/flatteners/flatten_type_to.js +1 -1
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/types/builders.d.ts +3 -3
- package/.out/types/builders.js +4 -6
- package/.out/validation/validator.d.ts +11 -9
- package/.out/validation/validator.js +6 -1
- package/.out/validation/validators/defined_validator.d.ts +1 -1
- package/.out/validation/validators/minimum_string_length_validator.d.ts +1 -1
- package/.out/validation/validators/minimum_string_length_validator.js +1 -1
- package/.out/validation/validators/optional_validator_proxy.d.ts +13 -0
- package/.out/validation/validators/optional_validator_proxy.js +26 -0
- package/.out/validation/validators/regexp_validator.d.ts +29 -0
- package/.out/validation/validators/regexp_validator.js +37 -0
- package/.out/validation/validators/specs/minimum_string_length_validator.tests.d.ts +1 -0
- package/.out/validation/validators/specs/minimum_string_length_validator.tests.js +69 -0
- package/.out/validation/validators/specs/regexp_validator.tests.d.ts +1 -0
- package/.out/validation/validators/specs/regexp_validator.tests.js +98 -0
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/dist/index.cjs +113 -33
- package/dist/index.d.cts +58 -15
- package/dist/index.d.ts +58 -15
- package/dist/index.js +109 -33
- package/index.ts +2 -0
- package/package.json +1 -1
- package/transformers/flatteners/flatten_type_to.ts +2 -2
- package/types/builders.ts +11 -14
- package/validation/validator.ts +26 -8
- package/validation/validators/defined_validator.ts +1 -3
- package/validation/validators/minimum_string_length_validator.ts +3 -3
- package/validation/validators/optional_validator_proxy.ts +52 -0
- package/validation/validators/regexp_validator.ts +61 -0
- package/validation/validators/specs/minimum_string_length_validator.tests.ts +76 -0
- package/validation/validators/specs/regexp_validator.tests.ts +108 -0
package/.out/types/builders.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type IsFieldReadonly } from '@strictly/base';
|
|
2
|
+
import { type Validator } from 'validation/validator';
|
|
2
3
|
import { type ObjectFieldKey, type RecordKeyType, type Type, type TypeDef, TypeDefType, type UnionKey } from './definitions';
|
|
3
4
|
import { type TypeOfType } from './type_of_type';
|
|
4
5
|
import { type Rule, type ValidatingListTypeDef, type ValidatingLiteralTypeDef, type ValidatingObjectTypeDef, type ValidatingRecordTypeDef, type ValidatingType, type ValidatingTypeDef, type ValidatingUnionTypeDef } from './validating_definitions';
|
|
@@ -7,9 +8,8 @@ import { type ValueOfType, type ValueOfTypeDef } from './value_of_type';
|
|
|
7
8
|
declare class TypeDefBuilder<T extends ValidatingTypeDef> implements ValidatingType<T> {
|
|
8
9
|
readonly definition: T;
|
|
9
10
|
constructor(definition: T);
|
|
10
|
-
enforce<E2>(rule: Rule<E2, ValueOfType<Type<T>>>): TypeDefBuilder<ValidatingTypeDefWithError<T, E2>>;
|
|
11
|
-
required(): TypeDefBuilder<
|
|
12
|
-
required<RequiredError>(rule: Rule<RequiredError, ValueOfType<typeof this.narrow>>): TypeDefBuilder<ValidatingTypeDefWithError<T, RequiredError>>;
|
|
11
|
+
enforce<E2>(rule: Rule<E2, ValueOfType<Type<T>>> | Validator<ValueOfType<Type<T>>, E2, never, never>): TypeDefBuilder<ValidatingTypeDefWithError<T, E2>>;
|
|
12
|
+
required(): TypeDefBuilder<T>;
|
|
13
13
|
readonly(): TypeDefBuilder<T>;
|
|
14
14
|
get _type(): TypeOfType<Type<T>>;
|
|
15
15
|
get narrow(): ValidatingType<T>;
|
package/.out/types/builders.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAnnotatedValidator, mergeAnnotations, validate, } from 'validation/validator';
|
|
1
2
|
import { TypeDefType, } from './definitions';
|
|
2
3
|
import { typeOfType, } from './type_of_type';
|
|
3
4
|
function emptyRule() {
|
|
@@ -13,22 +14,19 @@ class TypeDefBuilder {
|
|
|
13
14
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
14
15
|
{
|
|
15
16
|
...this.definition,
|
|
17
|
+
...(isAnnotatedValidator(rule) ? mergeAnnotations(rule.annotations(null, null), this.definition) : {}),
|
|
16
18
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
19
|
rule: (value) => {
|
|
18
|
-
return this.definition.rule(value)
|
|
20
|
+
return this.definition.rule(value) ?? validate(rule, value, null, null);
|
|
19
21
|
},
|
|
20
22
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
|
-
required(
|
|
25
|
+
required() {
|
|
24
26
|
return new TypeDefBuilder(
|
|
25
27
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
26
28
|
{
|
|
27
29
|
...this.definition,
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
-
rule: (v) => {
|
|
30
|
-
return this.definition.rule(v) || rule?.(v);
|
|
31
|
-
},
|
|
32
30
|
required: true,
|
|
33
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
32
|
});
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { type Simplify } from 'type-fest';
|
|
2
|
+
export type Annotations = {
|
|
3
|
+
readonly required: boolean;
|
|
4
|
+
readonly readonly: boolean;
|
|
5
|
+
};
|
|
2
6
|
export type FunctionalValidator<V = any, E = any, ValuePath extends string = any, Context = any> = (v: V, valuePath: ValuePath, context: Context) => E | null;
|
|
3
7
|
export type AnnotatedValidator<V = any, E = any, ValuePath extends string = any, Context = any> = {
|
|
4
8
|
readonly validate: (v: V, valuePath: ValuePath, context: Context) => E | null;
|
|
5
|
-
readonly annotations: (valuePath: ValuePath, context: Context) =>
|
|
6
|
-
readonly required: boolean;
|
|
7
|
-
readonly readonly: boolean;
|
|
8
|
-
};
|
|
9
|
+
readonly annotations: (valuePath: ValuePath, context: Context) => Annotations;
|
|
9
10
|
};
|
|
10
11
|
export type Validator<V = any, E = any, ValuePath extends string = any, Context = any> = FunctionalValidator<V, E, ValuePath, Context> | AnnotatedValidator<V, E, ValuePath, Context>;
|
|
11
12
|
export type ErrorOfValidator<V extends Validator> = V extends Validator<infer _V, infer E> ? E : never;
|
|
12
13
|
export type ValidationError<Type extends string, Data = {}> = Simplify<{
|
|
13
14
|
type: Type;
|
|
14
15
|
} & Data>;
|
|
15
|
-
export declare function isFunctionalValidator(v: Validator): v is FunctionalValidator
|
|
16
|
-
export declare function isAnnotatedValidator(v: Validator): v is AnnotatedValidator
|
|
16
|
+
export declare function isFunctionalValidator<V, E, ValuePath extends string, Context>(v: Validator<V, E, ValuePath, Context>): v is FunctionalValidator<V, E, ValuePath, Context>;
|
|
17
|
+
export declare function isAnnotatedValidator<V, E, ValuePath extends string, Context>(v: Validator<V, E, ValuePath, Context>): v is AnnotatedValidator<V, E, ValuePath, Context>;
|
|
17
18
|
export declare function validate<V = any, E = any, ValuePath extends string = any, Context = any>(validator: Validator<V, E, ValuePath, Context>, v: V, valuePath: ValuePath, context: Context): E | null;
|
|
18
|
-
export declare function annotations<V = any, E = any, ValuePath extends string = any, Context = any>(validator: Validator<V, E, ValuePath, Context>, valuePath: ValuePath, context: Context):
|
|
19
|
-
|
|
20
|
-
readonly
|
|
19
|
+
export declare function annotations<V = any, E = any, ValuePath extends string = any, Context = any>(validator: Validator<V, E, ValuePath, Context>, valuePath: ValuePath, context: Context): Annotations;
|
|
20
|
+
export declare function mergeAnnotations(a1: Annotations, a2: Annotations): {
|
|
21
|
+
readonly: boolean;
|
|
22
|
+
required: boolean;
|
|
21
23
|
};
|
|
@@ -9,7 +9,6 @@ export function validate(validator, v, valuePath, context) {
|
|
|
9
9
|
return validator.validate(v, valuePath, context);
|
|
10
10
|
}
|
|
11
11
|
else {
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
13
12
|
return validator(v, valuePath, context);
|
|
14
13
|
}
|
|
15
14
|
}
|
|
@@ -24,3 +23,9 @@ export function annotations(validator, valuePath, context) {
|
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
25
|
}
|
|
26
|
+
export function mergeAnnotations(a1, a2) {
|
|
27
|
+
return {
|
|
28
|
+
readonly: a1.readonly || a2.readonly,
|
|
29
|
+
required: a1.required || a2.required,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AnnotatedValidator } from 'validation/validator';
|
|
2
|
-
export declare class DefinedValidator<V, E
|
|
2
|
+
export declare class DefinedValidator<V, E> implements AnnotatedValidator<V | null | undefined, E, never, never> {
|
|
3
3
|
private readonly error;
|
|
4
4
|
constructor(error: E);
|
|
5
5
|
validate(v: V | null | undefined): E | null;
|
|
@@ -5,7 +5,7 @@ export type MinimumStringLengthValidationError = {
|
|
|
5
5
|
receivedLength: number;
|
|
6
6
|
minimumLength: number;
|
|
7
7
|
};
|
|
8
|
-
export declare class MinimumStringLengthValidator
|
|
8
|
+
export declare class MinimumStringLengthValidator implements AnnotatedValidator<string, MinimumStringLengthValidationError, never, never> {
|
|
9
9
|
private readonly minimumLength;
|
|
10
10
|
constructor(minimumLength: number);
|
|
11
11
|
validate(value: string): MinimumStringLengthValidationError | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnnotatedValidator } from 'validation/validator';
|
|
2
|
+
export declare class OptionalValidatorProxy<V, V1 extends V, E, ValuePath extends string, Context> implements AnnotatedValidator<V, E, ValuePath, Context> {
|
|
3
|
+
private readonly proxied;
|
|
4
|
+
private readonly isRequired;
|
|
5
|
+
static createNullable<V, E, ValuePath extends string, Context>(proxied: AnnotatedValidator<NonNullable<V>, E, ValuePath, Context>): OptionalValidatorProxy<V, NonNullable<V>, E, ValuePath, Context>;
|
|
6
|
+
static createNullableOrEmptyString<V extends string | null | undefined, E, ValuePath extends string, Context>(proxied: AnnotatedValidator<NonNullable<V>, E, ValuePath, Context>): OptionalValidatorProxy<V, NonNullable<V>, E, ValuePath, Context>;
|
|
7
|
+
constructor(proxied: AnnotatedValidator<V1, E, ValuePath, Context>, isRequired: (v: V) => v is V1);
|
|
8
|
+
validate(v: V, valuePath: ValuePath, context: Context): E | null;
|
|
9
|
+
annotations(valuePath: ValuePath, context: Context): {
|
|
10
|
+
required: boolean;
|
|
11
|
+
readonly: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class OptionalValidatorProxy {
|
|
2
|
+
proxied;
|
|
3
|
+
isRequired;
|
|
4
|
+
static createNullable(proxied) {
|
|
5
|
+
return new OptionalValidatorProxy(proxied, (v) => v != null);
|
|
6
|
+
}
|
|
7
|
+
static createNullableOrEmptyString(proxied) {
|
|
8
|
+
return new OptionalValidatorProxy(proxied, (v) => v != null && v !== '');
|
|
9
|
+
}
|
|
10
|
+
constructor(proxied, isRequired) {
|
|
11
|
+
this.proxied = proxied;
|
|
12
|
+
this.isRequired = isRequired;
|
|
13
|
+
}
|
|
14
|
+
validate(v, valuePath, context) {
|
|
15
|
+
if (this.isRequired(v)) {
|
|
16
|
+
return this.proxied.validate(v, valuePath, context);
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
annotations(valuePath, context) {
|
|
21
|
+
return {
|
|
22
|
+
...this.proxied.annotations(valuePath, context),
|
|
23
|
+
required: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type AnnotatedValidator } from 'validation/validator';
|
|
2
|
+
export declare const RegexpValidationErrorType = "regexp";
|
|
3
|
+
export type RegexpValidationError<Intent extends string> = {
|
|
4
|
+
type: typeof RegexpValidationErrorType;
|
|
5
|
+
intent: Intent;
|
|
6
|
+
};
|
|
7
|
+
export declare class RegexpValidator<Intent extends string> implements AnnotatedValidator<string, RegexpValidationError<Intent>, never, never> {
|
|
8
|
+
private readonly regexp;
|
|
9
|
+
private readonly intent;
|
|
10
|
+
/**
|
|
11
|
+
* Extremely permissive email validator
|
|
12
|
+
*/
|
|
13
|
+
static readonly email: RegexpValidator<"email">;
|
|
14
|
+
/**
|
|
15
|
+
* Extremely permissive phone number validator
|
|
16
|
+
*/
|
|
17
|
+
static readonly phone: RegexpValidator<"phone">;
|
|
18
|
+
private readonly negate;
|
|
19
|
+
private readonly required;
|
|
20
|
+
constructor(regexp: RegExp, intent: Intent, { negate, required, }?: {
|
|
21
|
+
negate?: boolean;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
});
|
|
24
|
+
validate(value: string): RegexpValidationError<Intent> | null;
|
|
25
|
+
annotations(): {
|
|
26
|
+
required: boolean;
|
|
27
|
+
readonly: boolean;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const RegexpValidationErrorType = 'regexp';
|
|
2
|
+
export class RegexpValidator {
|
|
3
|
+
regexp;
|
|
4
|
+
intent;
|
|
5
|
+
/**
|
|
6
|
+
* Extremely permissive email validator
|
|
7
|
+
*/
|
|
8
|
+
static email = new RegexpValidator(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'email');
|
|
9
|
+
/**
|
|
10
|
+
* Extremely permissive phone number validator
|
|
11
|
+
*/
|
|
12
|
+
static phone = new RegexpValidator(/^(\+\d{1,4}[\s]*)?(((\([\d\s-]{1,6}\))|\d)[\s-]*){3,14}(\d|(\(\d+\)))$/, 'phone');
|
|
13
|
+
negate;
|
|
14
|
+
required;
|
|
15
|
+
constructor(regexp, intent, { negate = false, required = false, } = {}) {
|
|
16
|
+
this.regexp = regexp;
|
|
17
|
+
this.intent = intent;
|
|
18
|
+
this.negate = negate;
|
|
19
|
+
this.required = required;
|
|
20
|
+
}
|
|
21
|
+
validate(value) {
|
|
22
|
+
const passes = this.regexp.test(value);
|
|
23
|
+
if (!passes && !this.negate || passes && this.negate) {
|
|
24
|
+
return {
|
|
25
|
+
type: RegexpValidationErrorType,
|
|
26
|
+
intent: this.intent,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
annotations() {
|
|
32
|
+
return {
|
|
33
|
+
required: this.required,
|
|
34
|
+
readonly: false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MinimumStringLengthValidationErrorType, MinimumStringLengthValidator, } from 'validation/validators/minimum_string_length_validator';
|
|
2
|
+
describe('MinimumStringLengthValidator', () => {
|
|
3
|
+
describe('required', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
1,
|
|
6
|
+
2,
|
|
7
|
+
100,
|
|
8
|
+
])('is required when the string length is %s', (minimumLength) => {
|
|
9
|
+
const validator = new MinimumStringLengthValidator(minimumLength);
|
|
10
|
+
expect(validator.annotations().required).toBeTruthy();
|
|
11
|
+
});
|
|
12
|
+
it('is not required when the string length is zero', () => {
|
|
13
|
+
const validator = new MinimumStringLengthValidator(0);
|
|
14
|
+
expect(validator.annotations().required).toBeFalsy();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe('validation', () => {
|
|
18
|
+
it.each([
|
|
19
|
+
[
|
|
20
|
+
1,
|
|
21
|
+
'a',
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
2,
|
|
25
|
+
'asdf',
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
0,
|
|
29
|
+
'',
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
20,
|
|
33
|
+
'12345678901234567890',
|
|
34
|
+
],
|
|
35
|
+
])('passes validation with minimum length %s and value "%s"', (minimumLength, value) => {
|
|
36
|
+
const validator = new MinimumStringLengthValidator(minimumLength);
|
|
37
|
+
expect(validator.validate(value)).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it.each([
|
|
40
|
+
[
|
|
41
|
+
1,
|
|
42
|
+
'',
|
|
43
|
+
0,
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
2,
|
|
47
|
+
'a',
|
|
48
|
+
1,
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
20,
|
|
52
|
+
'1234567890123456789',
|
|
53
|
+
19,
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
100,
|
|
57
|
+
'',
|
|
58
|
+
0,
|
|
59
|
+
],
|
|
60
|
+
])('fails validation with minimum length %s and value "%s', (minimumLength, value, receivedLength) => {
|
|
61
|
+
const validator = new MinimumStringLengthValidator(minimumLength);
|
|
62
|
+
expect(validator.validate(value)).toEqual({
|
|
63
|
+
type: MinimumStringLengthValidationErrorType,
|
|
64
|
+
minimumLength,
|
|
65
|
+
receivedLength,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { RegexpValidationErrorType, RegexpValidator, } from 'validation/validators/regexp_validator';
|
|
2
|
+
describe('RegexpValidator', () => {
|
|
3
|
+
describe('phone', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
'0403545000',
|
|
6
|
+
'+12 (1800) 000 000',
|
|
7
|
+
'1-2-3-4-5-6',
|
|
8
|
+
'+1 394 3234 000 (2)',
|
|
9
|
+
'1234',
|
|
10
|
+
])('accepts "%s"', (phoneNumber) => {
|
|
11
|
+
expect(RegexpValidator.phone.validate(phoneNumber)).toBeNull();
|
|
12
|
+
});
|
|
13
|
+
it.each([
|
|
14
|
+
'',
|
|
15
|
+
'ABC',
|
|
16
|
+
'1',
|
|
17
|
+
' 0412345678',
|
|
18
|
+
'----1245456',
|
|
19
|
+
'1 2',
|
|
20
|
+
'234234+234',
|
|
21
|
+
])('rejects "%s"', (phoneNumber) => {
|
|
22
|
+
expect(RegexpValidator.phone.validate(phoneNumber)).toEqual({
|
|
23
|
+
type: RegexpValidationErrorType,
|
|
24
|
+
intent: 'phone',
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('email', () => {
|
|
29
|
+
it.each([
|
|
30
|
+
'x@y.z',
|
|
31
|
+
'support@company.com',
|
|
32
|
+
'...@......',
|
|
33
|
+
'1234@3454.23',
|
|
34
|
+
])('accepts "%s"', (email) => {
|
|
35
|
+
expect(RegexpValidator.email.validate(email)).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
it.each([
|
|
38
|
+
'',
|
|
39
|
+
'@@@@',
|
|
40
|
+
'email',
|
|
41
|
+
'@bee.com',
|
|
42
|
+
'aaa@bbb',
|
|
43
|
+
'a a@b b.c c',
|
|
44
|
+
])('rejects "%s"', (email) => {
|
|
45
|
+
expect(RegexpValidator.email.validate(email)).toEqual({
|
|
46
|
+
type: RegexpValidationErrorType,
|
|
47
|
+
intent: 'email',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('required', () => {
|
|
52
|
+
it.each([
|
|
53
|
+
true,
|
|
54
|
+
false,
|
|
55
|
+
])('exposes required %s', (required) => {
|
|
56
|
+
const validator = new RegexpValidator(/a/, 'test', {
|
|
57
|
+
required,
|
|
58
|
+
});
|
|
59
|
+
expect(validator.annotations().required).toBe(required);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('general', () => {
|
|
63
|
+
// not here to test if regexp works
|
|
64
|
+
it.each([
|
|
65
|
+
[
|
|
66
|
+
'^a$',
|
|
67
|
+
'a',
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
'^\\w+$',
|
|
71
|
+
'asdf',
|
|
72
|
+
],
|
|
73
|
+
])('passes validation with regexp "%s" and value "%s"', (regexpString, value) => {
|
|
74
|
+
const regexp = new RegExp(regexpString);
|
|
75
|
+
const validator = new RegexpValidator(regexp, 'test');
|
|
76
|
+
expect(validator.validate(value)).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
it.each([
|
|
79
|
+
[
|
|
80
|
+
'^$',
|
|
81
|
+
'something',
|
|
82
|
+
'empty',
|
|
83
|
+
],
|
|
84
|
+
[
|
|
85
|
+
'^\\w+$',
|
|
86
|
+
'',
|
|
87
|
+
'non-empty',
|
|
88
|
+
],
|
|
89
|
+
])('fails validation with regexp "%s" and value "%s', (regexpString, value, intent) => {
|
|
90
|
+
const regexp = new RegExp(regexpString);
|
|
91
|
+
const validator = new RegexpValidator(regexp, intent);
|
|
92
|
+
expect(validator.validate(value)).toEqual({
|
|
93
|
+
type: RegexpValidationErrorType,
|
|
94
|
+
intent,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,12 +7,12 @@ $ tsup
|
|
|
7
7
|
[34mCLI[39m Target: esnext
|
|
8
8
|
[34mCJS[39m Build start
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
10
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m29.06 KB[39m
|
|
11
|
+
[32mCJS[39m ⚡️ Build success in 62ms
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m26.67 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 63ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
18
|
-
Done in 6.
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 5551ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m40.10 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m40.10 KB[39m
|
|
18
|
+
Done in 6.43s.
|
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,9 @@ __export(index_exports, {
|
|
|
23
23
|
DefinedValidator: () => DefinedValidator,
|
|
24
24
|
MinimumStringLengthValidationErrorType: () => MinimumStringLengthValidationErrorType,
|
|
25
25
|
MinimumStringLengthValidator: () => MinimumStringLengthValidator,
|
|
26
|
+
OptionalValidatorProxy: () => OptionalValidatorProxy,
|
|
27
|
+
RegexpValidationErrorType: () => RegexpValidationErrorType,
|
|
28
|
+
RegexpValidator: () => RegexpValidator,
|
|
26
29
|
TypeDefType: () => TypeDefType,
|
|
27
30
|
annotations: () => annotations,
|
|
28
31
|
booleanType: () => booleanType,
|
|
@@ -40,6 +43,7 @@ __export(index_exports, {
|
|
|
40
43
|
jsonPathPop: () => jsonPathPop,
|
|
41
44
|
list: () => list,
|
|
42
45
|
literal: () => literal,
|
|
46
|
+
mergeAnnotations: () => mergeAnnotations,
|
|
43
47
|
mobxCopy: () => mobxCopy,
|
|
44
48
|
nullType: () => nullType,
|
|
45
49
|
nullable: () => nullable,
|
|
@@ -430,7 +434,7 @@ function flattenTypeTo({ definition }, mapper2) {
|
|
|
430
434
|
return (0, import_base4.reduce)(
|
|
431
435
|
typeDefs,
|
|
432
436
|
function(acc, key, typeDef) {
|
|
433
|
-
acc[key] = mapper2(typeDef);
|
|
437
|
+
acc[key] = mapper2(typeDef, key);
|
|
434
438
|
return acc;
|
|
435
439
|
},
|
|
436
440
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -671,6 +675,37 @@ function internalJsonValuePathToTypePath(typeDef, valueSteps, allowMissingPaths,
|
|
|
671
675
|
}
|
|
672
676
|
}
|
|
673
677
|
|
|
678
|
+
// validation/validator.ts
|
|
679
|
+
function isFunctionalValidator(v) {
|
|
680
|
+
return typeof v === "function";
|
|
681
|
+
}
|
|
682
|
+
function isAnnotatedValidator(v) {
|
|
683
|
+
return typeof v !== "function";
|
|
684
|
+
}
|
|
685
|
+
function validate(validator, v, valuePath, context) {
|
|
686
|
+
if (isAnnotatedValidator(validator)) {
|
|
687
|
+
return validator.validate(v, valuePath, context);
|
|
688
|
+
} else {
|
|
689
|
+
return validator(v, valuePath, context);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function annotations(validator, valuePath, context) {
|
|
693
|
+
if (isAnnotatedValidator(validator)) {
|
|
694
|
+
return validator.annotations(valuePath, context);
|
|
695
|
+
} else {
|
|
696
|
+
return {
|
|
697
|
+
required: false,
|
|
698
|
+
readonly: false
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function mergeAnnotations(a1, a2) {
|
|
703
|
+
return {
|
|
704
|
+
readonly: a1.readonly || a2.readonly,
|
|
705
|
+
required: a1.required || a2.required
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
674
709
|
// types/type_of_type.ts
|
|
675
710
|
var import_base6 = require("@strictly/base");
|
|
676
711
|
function typeOfType({ definition }) {
|
|
@@ -761,23 +796,20 @@ var TypeDefBuilder = class _TypeDefBuilder {
|
|
|
761
796
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
762
797
|
{
|
|
763
798
|
...this.definition,
|
|
799
|
+
...isAnnotatedValidator(rule) ? mergeAnnotations(rule.annotations(null, null), this.definition) : {},
|
|
764
800
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
765
801
|
rule: (value) => {
|
|
766
|
-
return this.definition.rule(value)
|
|
802
|
+
return this.definition.rule(value) ?? validate(rule, value, null, null);
|
|
767
803
|
}
|
|
768
804
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
769
805
|
}
|
|
770
806
|
);
|
|
771
807
|
}
|
|
772
|
-
required(
|
|
808
|
+
required() {
|
|
773
809
|
return new _TypeDefBuilder(
|
|
774
810
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
775
811
|
{
|
|
776
812
|
...this.definition,
|
|
777
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
778
|
-
rule: (v) => {
|
|
779
|
-
return this.definition.rule(v) || rule?.(v);
|
|
780
|
-
},
|
|
781
813
|
required: true
|
|
782
814
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
783
815
|
}
|
|
@@ -982,31 +1014,6 @@ function union(discriminator) {
|
|
|
982
1014
|
);
|
|
983
1015
|
}
|
|
984
1016
|
|
|
985
|
-
// validation/validator.ts
|
|
986
|
-
function isFunctionalValidator(v) {
|
|
987
|
-
return typeof v === "function";
|
|
988
|
-
}
|
|
989
|
-
function isAnnotatedValidator(v) {
|
|
990
|
-
return typeof v !== "function";
|
|
991
|
-
}
|
|
992
|
-
function validate(validator, v, valuePath, context) {
|
|
993
|
-
if (isAnnotatedValidator(validator)) {
|
|
994
|
-
return validator.validate(v, valuePath, context);
|
|
995
|
-
} else {
|
|
996
|
-
return validator(v, valuePath, context);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
function annotations(validator, valuePath, context) {
|
|
1000
|
-
if (isAnnotatedValidator(validator)) {
|
|
1001
|
-
return validator.annotations(valuePath, context);
|
|
1002
|
-
} else {
|
|
1003
|
-
return {
|
|
1004
|
-
required: false,
|
|
1005
|
-
readonly: false
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
1017
|
// validation/validators/defined_validator.ts
|
|
1011
1018
|
var DefinedValidator = class {
|
|
1012
1019
|
constructor(error) {
|
|
@@ -1044,7 +1051,76 @@ var MinimumStringLengthValidator = class {
|
|
|
1044
1051
|
}
|
|
1045
1052
|
annotations() {
|
|
1046
1053
|
return {
|
|
1047
|
-
required:
|
|
1054
|
+
required: this.minimumLength > 0,
|
|
1055
|
+
readonly: false
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
// validation/validators/optional_validator_proxy.ts
|
|
1061
|
+
var OptionalValidatorProxy = class _OptionalValidatorProxy {
|
|
1062
|
+
constructor(proxied, isRequired) {
|
|
1063
|
+
this.proxied = proxied;
|
|
1064
|
+
this.isRequired = isRequired;
|
|
1065
|
+
}
|
|
1066
|
+
static createNullable(proxied) {
|
|
1067
|
+
return new _OptionalValidatorProxy(proxied, (v) => v != null);
|
|
1068
|
+
}
|
|
1069
|
+
static createNullableOrEmptyString(proxied) {
|
|
1070
|
+
return new _OptionalValidatorProxy(proxied, (v) => v != null && v !== "");
|
|
1071
|
+
}
|
|
1072
|
+
validate(v, valuePath, context) {
|
|
1073
|
+
if (this.isRequired(v)) {
|
|
1074
|
+
return this.proxied.validate(v, valuePath, context);
|
|
1075
|
+
}
|
|
1076
|
+
return null;
|
|
1077
|
+
}
|
|
1078
|
+
annotations(valuePath, context) {
|
|
1079
|
+
return {
|
|
1080
|
+
...this.proxied.annotations(valuePath, context),
|
|
1081
|
+
required: false
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// validation/validators/regexp_validator.ts
|
|
1087
|
+
var RegexpValidationErrorType = "regexp";
|
|
1088
|
+
var RegexpValidator = class _RegexpValidator {
|
|
1089
|
+
constructor(regexp, intent, {
|
|
1090
|
+
negate = false,
|
|
1091
|
+
required = false
|
|
1092
|
+
} = {}) {
|
|
1093
|
+
this.regexp = regexp;
|
|
1094
|
+
this.intent = intent;
|
|
1095
|
+
this.negate = negate;
|
|
1096
|
+
this.required = required;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Extremely permissive email validator
|
|
1100
|
+
*/
|
|
1101
|
+
static email = new _RegexpValidator(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, "email");
|
|
1102
|
+
/**
|
|
1103
|
+
* Extremely permissive phone number validator
|
|
1104
|
+
*/
|
|
1105
|
+
static phone = new _RegexpValidator(
|
|
1106
|
+
/^(\+\d{1,4}[\s]*)?(((\([\d\s-]{1,6}\))|\d)[\s-]*){3,14}(\d|(\(\d+\)))$/,
|
|
1107
|
+
"phone"
|
|
1108
|
+
);
|
|
1109
|
+
negate;
|
|
1110
|
+
required;
|
|
1111
|
+
validate(value) {
|
|
1112
|
+
const passes = this.regexp.test(value);
|
|
1113
|
+
if (!passes && !this.negate || passes && this.negate) {
|
|
1114
|
+
return {
|
|
1115
|
+
type: RegexpValidationErrorType,
|
|
1116
|
+
intent: this.intent
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
annotations() {
|
|
1122
|
+
return {
|
|
1123
|
+
required: this.required,
|
|
1048
1124
|
readonly: false
|
|
1049
1125
|
};
|
|
1050
1126
|
}
|
|
@@ -1054,6 +1130,9 @@ var MinimumStringLengthValidator = class {
|
|
|
1054
1130
|
DefinedValidator,
|
|
1055
1131
|
MinimumStringLengthValidationErrorType,
|
|
1056
1132
|
MinimumStringLengthValidator,
|
|
1133
|
+
OptionalValidatorProxy,
|
|
1134
|
+
RegexpValidationErrorType,
|
|
1135
|
+
RegexpValidator,
|
|
1057
1136
|
TypeDefType,
|
|
1058
1137
|
annotations,
|
|
1059
1138
|
booleanType,
|
|
@@ -1071,6 +1150,7 @@ var MinimumStringLengthValidator = class {
|
|
|
1071
1150
|
jsonPathPop,
|
|
1072
1151
|
list,
|
|
1073
1152
|
literal,
|
|
1153
|
+
mergeAnnotations,
|
|
1074
1154
|
mobxCopy,
|
|
1075
1155
|
nullType,
|
|
1076
1156
|
nullable,
|