@fluojs/validation 1.0.0-beta.2 → 1.0.0-beta.4
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/README.ko.md +55 -57
- package/README.md +23 -3
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +2 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +22 -15
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
fluo를 위한 입력값 검증 데코레이터, Mapped DTO 헬퍼 및 검증 엔진입니다.
|
|
6
6
|
|
|
7
|
-
`@fluojs/validation`은 애플리케이션의 **입력 경계(Input Boundary)**를 담당합니다. 가공되지 않은(untyped) raw 데이터를 검증이 완료된 타입 기반 클래스 인스턴스(DTO)로 변환하는 강력한 데코레이터 세트와 실체화(Materialization) 엔진을 제공합니다. 이를 통해 비즈니스 로직에 도달하기 전 데이터의 무결성을 보장합니다.
|
|
8
|
-
|
|
9
7
|
## 목차
|
|
10
8
|
|
|
11
9
|
- [설치](#설치)
|
|
@@ -24,48 +22,59 @@ pnpm add @fluojs/validation
|
|
|
24
22
|
|
|
25
23
|
## 사용 시점
|
|
26
24
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Zod나 Valibot 같은
|
|
25
|
+
- raw request payload를 비즈니스 로직에 도달하기 전에 검증된 DTO 인스턴스로 바꿔야 할 때
|
|
26
|
+
- 컨트롤러나 서비스에서 ad hoc parsing 대신 class 기반 검증 규칙을 쓰고 싶을 때
|
|
27
|
+
- `PickType`, `PartialType`, `IntersectionType` 같은 metadata-preserving mapped DTO helper가 필요할 때
|
|
28
|
+
- `@ValidateClass(...)`로 Zod나 Valibot 같은 Standard Schema validator를 붙이고 싶을 때
|
|
31
29
|
|
|
32
30
|
## 빠른 시작
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
import { IsEmail, IsString, MinLength, DefaultValidator } from '@fluojs/validation';
|
|
32
|
+
```ts
|
|
33
|
+
import { DefaultValidator, DtoValidationError, IsEmail, IsString, MinLength } from '@fluojs/validation';
|
|
38
34
|
|
|
39
35
|
class CreateUserDto {
|
|
40
36
|
@IsEmail()
|
|
41
|
-
email
|
|
37
|
+
email = '';
|
|
42
38
|
|
|
43
39
|
@IsString()
|
|
44
40
|
@MinLength(2)
|
|
45
|
-
name
|
|
41
|
+
name = '';
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
const validator = new DefaultValidator();
|
|
49
|
-
const rawData = { email: 'test@example.com', name: 'Ko' };
|
|
50
|
-
|
|
51
|
-
// materialize()는 CreateUserDto의 인스턴스를 생성하고 검증을 수행합니다.
|
|
52
|
-
const user = await validator.materialize(rawData, CreateUserDto);
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
try {
|
|
47
|
+
const dto = await validator.materialize(
|
|
48
|
+
{ email: 'hello@example.com', name: 'fluo' },
|
|
49
|
+
CreateUserDto,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
console.log(dto instanceof CreateUserDto);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof DtoValidationError) {
|
|
55
|
+
console.log(error.issues);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
## 주요 패턴
|
|
59
61
|
|
|
60
|
-
###
|
|
62
|
+
### `materialize()` vs `validate()`
|
|
61
63
|
|
|
62
64
|
- **`materialize<T>(value, target)`**: **입력 처리**에 가장 적합합니다. plain 객체를 받아 대상 클래스의 인스턴스를 생성하고, 값을 복사하며, 중첩된 DTO를 재귀적으로 처리한 후 모든 검증 규칙을 실행합니다.
|
|
63
65
|
- **`validate(instance, target)`**: **기존 루트 객체 확인**에 적합합니다. 이미 생성된 루트 값에 대해 검증 규칙을 실행하며, plain 객체인 `@ValidateNested(...)` 값은 중첩 DTO 규칙을 실행하기 위해 임시로 실체화할 수 있습니다. 이 임시 실체화는 호출자가 넘긴 속성 값을 대체하지 않습니다.
|
|
64
66
|
|
|
67
|
+
`validate()`는 문자열, 배열, `null`, `undefined` 같은 잘못된 루트 값을 field 또는
|
|
68
|
+
class rule이 실행되기 전에 deterministic `DtoValidationError`로 거부합니다. 이미
|
|
69
|
+
생성된 대상 DTO 인스턴스와 plain 루트 객체는 허용하므로 request-pipeline binder가
|
|
70
|
+
준비한 DTO payload를 scalar coercion 없이 검증할 수 있습니다.
|
|
71
|
+
|
|
65
72
|
`materialize()`는 plain 입력 객체의 안전한 own enumerable 속성을 복사하고,
|
|
66
73
|
DTO 바인딩 메타데이터를 적용한 뒤 `@ValidateNested(...)` 필드를 재귀적으로
|
|
67
74
|
실체화합니다. 어떤 요청 소스를 선택하고 스칼라 값을 변환할지는 transport 또는
|
|
68
75
|
binder가 검증 전에 담당한다는 request-pipeline 계약을 유지합니다.
|
|
76
|
+
선언된 중첩 DTO의 인스턴스인 기존 중첩 값은 그대로 보존하고, plain 중첩 값만
|
|
77
|
+
해당 필드 또는 collection entry 단위로 실체화합니다.
|
|
69
78
|
`materialize()`에 넘기는 루트 값은 plain 객체이거나 대상 DTO 인스턴스여야 합니다.
|
|
70
79
|
문자열, 배열, `null` 같은 잘못된 루트 값은 대상 DTO 생성자나 필드 initializer가
|
|
71
80
|
실행되기 전에 거부됩니다.
|
|
@@ -87,81 +96,70 @@ type ValidationIssue = {
|
|
|
87
96
|
사용합니다. HTTP 바인딩에서 온 규칙은 `source`를 붙이며, standalone validation이나
|
|
88
97
|
Standard Schema 이슈에서는 값이 없을 수 있습니다.
|
|
89
98
|
|
|
90
|
-
### Mapped
|
|
91
|
-
|
|
92
|
-
모든 검증 데코레이터와 바인딩 메타데이터를 보존하면서 새로운 DTO 클래스를 파생합니다.
|
|
99
|
+
### Mapped DTO 헬퍼
|
|
93
100
|
|
|
94
|
-
```
|
|
101
|
+
```ts
|
|
95
102
|
import { IsString, IsEmail, PickType, PartialType } from '@fluojs/validation';
|
|
96
103
|
|
|
97
104
|
class UserDto {
|
|
98
|
-
@IsString() name
|
|
99
|
-
@IsEmail() email
|
|
105
|
+
@IsString() name = '';
|
|
106
|
+
@IsEmail() email = '';
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
// 'email' 필드만 포함
|
|
103
109
|
class EmailOnlyDto extends PickType(UserDto, ['email']) {}
|
|
104
|
-
|
|
105
|
-
// 모든 필드를 선택 사항(optional)으로 변경
|
|
106
110
|
class UpdateUserDto extends PartialType(UserDto) {}
|
|
107
111
|
```
|
|
108
112
|
|
|
109
|
-
### Standard Schema 지원
|
|
113
|
+
### Standard Schema 지원
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
유효하지 않은 입력은 명시적인 `issues`로 보고되어야 하며, 이슈가 없는 검증 결과는 성공으로 처리합니다.
|
|
115
|
+
Standard Schema adapter는 유효하지 않은 입력을 명시적인 issue로 보고해야 합니다. issue가 없는 검증 결과는 성공으로 처리합니다.
|
|
113
116
|
|
|
114
|
-
```
|
|
117
|
+
```ts
|
|
115
118
|
import { ValidateClass } from '@fluojs/validation';
|
|
116
119
|
import { z } from 'zod';
|
|
117
120
|
|
|
118
|
-
const UserSchema = z.object({
|
|
119
|
-
age: z.number().min(18),
|
|
120
|
-
});
|
|
121
|
+
const UserSchema = z.object({ age: z.number().min(18) });
|
|
121
122
|
|
|
122
123
|
@ValidateClass(UserSchema)
|
|
123
124
|
class RestrictedUserDto {
|
|
124
|
-
age
|
|
125
|
+
age = 0;
|
|
125
126
|
}
|
|
126
127
|
```
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
`ValidateClass(...)`는 custom class-level validator도 받을 수 있습니다. `Validate(...)`는 built-in decorator만으로 부족할 때 custom field-level validator를 붙이고, `ValidateIf(...)`는 predicate가 false를 반환하면 dependent validator를 short-circuit합니다.
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
### 중첩 검증
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
`@ValidateNested(...)`는 객체 필드, 배열, `Set`, `Map`을 지원합니다. 중첩 DTO path는 validation issue에서 dot/index 표기법을 사용하며, cycle은 안전하게 감지되고 shared reference는 허용됩니다.
|
|
134
|
+
중첩 타입을 지연 해석해야 할 때는 DTO 클래스 자체나 `() => ChildDto`, `function resolveChildDto() { return ChildDto; }` 같은 lazy constructor factory를 전달할 수 있습니다.
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
@IsString() bio: string = '';
|
|
137
|
-
}
|
|
136
|
+
### 암묵적 scalar coercion 없음
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
@ValidateNested(() => ProfileDto)
|
|
143
|
-
profile?: ProfileDto;
|
|
144
|
-
}
|
|
145
|
-
```
|
|
138
|
+
`materialize()`는 의도적으로 엄격합니다. Transport가 `'42'`를 넘기고 DTO가 `number`를 기대한다면, transport나 binding layer가 먼저 변환해야 합니다.
|
|
139
|
+
`@IsLatitude()`, `@IsLongitude()`를 포함한 numeric validator는 numeric string을 이미 변환된 number처럼 취급하지 않고 DTO의 numeric 값을 검증합니다.
|
|
146
140
|
|
|
147
141
|
## 공개 API
|
|
148
142
|
|
|
149
143
|
- **검증 엔진**: `DefaultValidator`, `DtoValidationError`, `ValidationIssue`, `Validator`
|
|
150
144
|
- **핵심 데코레이터**: `IsString`, `IsNumber`, `IsBoolean`, `IsDate`, `IsArray`, `IsObject`, `IsEnum`, `IsInt`, `IsDefined`, `IsOptional`, `ValidateNested`, `ValidateIf`, `Validate`, `ValidateClass`
|
|
151
|
-
-
|
|
152
|
-
-
|
|
145
|
+
- **존재 및 비교 데코레이터**: `IsEmpty`, `IsNotEmpty`, `Equals`, `NotEquals`, `IsIn`, `IsNotIn`
|
|
146
|
+
- **문자열 및 네트워크 데코레이터**: `IsEmail`, `IsUrl`, `IsUUID`, `IsIP`, `IsAlpha`, `IsAlphanumeric`, `IsAscii`, `IsBase64`, `IsBooleanString`, `IsDataURI`, `IsDateString`, `IsDecimal`, `IsFQDN`, `IsHexColor`, `IsHexadecimal`, `IsJSON`, `IsJWT`, `IsLocale`, `IsLowercase`, `IsMagnetURI`, `IsMimeType`, `IsMongoId`, `IsNumberString`, `IsPort`, `IsRFC3339`, `IsSemVer`, `IsUppercase`, `IsISO8601`, `Matches`, `Length`, `MinLength`, `MaxLength`, `Contains`, `NotContains`
|
|
147
|
+
- **숫자, 날짜, 지리, locale 데코레이터**: `Min`, `Max`, `IsPositive`, `IsNegative`, `IsDivisibleBy`, `MinDate`, `MaxDate`, `IsLatitude`, `IsLongitude`, `IsLatLong`, `IsISBN`, `IsISSN`, `IsMobilePhone`, `IsPostalCode`, `IsRgbColor`, `IsCurrency`
|
|
153
148
|
- **배열 데코레이터**: `ArrayContains`, `ArrayNotContains`, `ArrayNotEmpty`, `ArrayMinSize`, `ArrayMaxSize`, `ArrayUnique`
|
|
154
149
|
- **Mapped DTO 헬퍼**: `PickType`, `OmitType`, `PartialType`, `IntersectionType`
|
|
150
|
+
- **Mapped DTO 서브패스**: `@fluojs/validation/mapped-types`
|
|
155
151
|
- **Standard Schema 계약**: `ValidateClass(...)` 스키마를 타입 지정하기 위한 `StandardSchemaV1Like`
|
|
156
152
|
- **검증 흐름**: 실체화 및 검증을 위한 `materialize()`, 단순 검증을 위한 `validate()`
|
|
157
153
|
|
|
158
154
|
## 관련 패키지
|
|
159
155
|
|
|
160
|
-
- `@fluojs/
|
|
161
|
-
- `@fluojs/
|
|
162
|
-
- `@fluojs/
|
|
156
|
+
- `@fluojs/http`: request data를 bind한 뒤 이 패키지로 검증합니다.
|
|
157
|
+
- `@fluojs/serialization`: response side에서 output DTO를 가공합니다.
|
|
158
|
+
- `@fluojs/core`: validation decorator가 사용하는 metadata primitive를 제공합니다.
|
|
163
159
|
|
|
164
160
|
## 예제 소스
|
|
165
161
|
|
|
166
|
-
- `packages/validation/src/validation.test.ts
|
|
167
|
-
- `
|
|
162
|
+
- `packages/validation/src/validation.test.ts`
|
|
163
|
+
- `packages/validation/src/mapped-types.test.ts`
|
|
164
|
+
- `examples/realworld-api/src/users/create-user.dto.ts`
|
|
165
|
+
- `examples/auth-jwt-passport/src/auth/login.dto.ts`
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
|
|
4
4
|
|
|
5
|
-
Input-side validation decorators, mapped DTO helpers, and the
|
|
5
|
+
Input-side validation decorators, mapped DTO helpers, and the validation engine for fluo.
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
|
|
@@ -66,10 +66,19 @@ try {
|
|
|
66
66
|
temporarily materialize plain nested `@ValidateNested(...)` values to run their
|
|
67
67
|
nested DTO rules without replacing the caller's properties
|
|
68
68
|
|
|
69
|
+
`validate()` rejects malformed roots such as strings, arrays, `null`, and
|
|
70
|
+
`undefined` with a deterministic `DtoValidationError` before field or class rules
|
|
71
|
+
run. It accepts already-created target DTO instances and plain root objects so
|
|
72
|
+
request-pipeline binders can validate their prepared DTO payloads without scalar
|
|
73
|
+
coercion.
|
|
74
|
+
|
|
69
75
|
`materialize()` copies safe own enumerable properties from plain input objects,
|
|
70
76
|
applies DTO binding metadata, and recursively hydrates `@ValidateNested(...)`
|
|
71
77
|
fields. It preserves the request-pipeline contract that transports or binders own
|
|
72
78
|
source selection and scalar conversion before validation runs.
|
|
79
|
+
Existing nested values that are already instances of the declared nested DTO are
|
|
80
|
+
preserved; plain nested values are hydrated only for the affected nested field or
|
|
81
|
+
collection entry.
|
|
73
82
|
The root value passed to `materialize()` must already be a plain object or an
|
|
74
83
|
instance of the target DTO; malformed roots such as strings, arrays, and `null`
|
|
75
84
|
are rejected before the target DTO constructor or field initializers run.
|
|
@@ -121,18 +130,28 @@ class RestrictedUserDto {
|
|
|
121
130
|
}
|
|
122
131
|
```
|
|
123
132
|
|
|
133
|
+
`ValidateClass(...)` also accepts custom class-level validators. `Validate(...)` attaches custom field-level validators when built-in decorators are not enough, and `ValidateIf(...)` short-circuits dependent validators when its predicate returns false.
|
|
134
|
+
|
|
135
|
+
### Nested validation
|
|
136
|
+
|
|
137
|
+
`@ValidateNested(...)` supports object fields, arrays, `Set`, and `Map`. Nested DTO paths use dot/index notation in validation issues, cycles are detected safely, and shared references are allowed.
|
|
138
|
+
Pass either a DTO class or a lazy constructor factory such as `() => ChildDto` or `function resolveChildDto() { return ChildDto; }` when nested types need deferred resolution.
|
|
139
|
+
|
|
124
140
|
### No implicit scalar coercion
|
|
125
141
|
|
|
126
142
|
`materialize()` is intentionally strict. If a transport gives you `'42'` and your DTO expects `number`, the transport or binding layer must convert it first.
|
|
143
|
+
Numeric validators, including `@IsLatitude()` and `@IsLongitude()`, validate numeric DTO values without treating numeric strings as already-converted numbers.
|
|
127
144
|
|
|
128
145
|
## Public API
|
|
129
146
|
|
|
130
147
|
- **Validator engine**: `DefaultValidator`, `DtoValidationError`, `ValidationIssue`, `Validator`
|
|
131
148
|
- **Core decorators**: `IsString`, `IsNumber`, `IsBoolean`, `IsDate`, `IsArray`, `IsObject`, `IsEnum`, `IsInt`, `IsDefined`, `IsOptional`, `ValidateNested`, `ValidateIf`, `Validate`, `ValidateClass`
|
|
132
|
-
- **
|
|
133
|
-
- **
|
|
149
|
+
- **Presence and comparison decorators**: `IsEmpty`, `IsNotEmpty`, `Equals`, `NotEquals`, `IsIn`, `IsNotIn`
|
|
150
|
+
- **String and network decorators**: `IsEmail`, `IsUrl`, `IsUUID`, `IsIP`, `IsAlpha`, `IsAlphanumeric`, `IsAscii`, `IsBase64`, `IsBooleanString`, `IsDataURI`, `IsDateString`, `IsDecimal`, `IsFQDN`, `IsHexColor`, `IsHexadecimal`, `IsJSON`, `IsJWT`, `IsLocale`, `IsLowercase`, `IsMagnetURI`, `IsMimeType`, `IsMongoId`, `IsNumberString`, `IsPort`, `IsRFC3339`, `IsSemVer`, `IsUppercase`, `IsISO8601`, `Matches`, `Length`, `MinLength`, `MaxLength`, `Contains`, `NotContains`
|
|
151
|
+
- **Number, date, geo, and locale decorators**: `Min`, `Max`, `IsPositive`, `IsNegative`, `IsDivisibleBy`, `MinDate`, `MaxDate`, `IsLatitude`, `IsLongitude`, `IsLatLong`, `IsISBN`, `IsISSN`, `IsMobilePhone`, `IsPostalCode`, `IsRgbColor`, `IsCurrency`
|
|
134
152
|
- **Array decorators**: `ArrayContains`, `ArrayNotContains`, `ArrayNotEmpty`, `ArrayMinSize`, `ArrayMaxSize`, `ArrayUnique`
|
|
135
153
|
- **Mapped DTO helpers**: `PickType`, `OmitType`, `PartialType`, `IntersectionType`
|
|
154
|
+
- **Mapped DTO subpath**: `@fluojs/validation/mapped-types`
|
|
136
155
|
- **Standard Schema contract**: `StandardSchemaV1Like` for typing `ValidateClass(...)` schemas
|
|
137
156
|
- **Validation flow**: `materialize()` for hydration + validation, `validate()` for validation-only checks
|
|
138
157
|
|
|
@@ -145,5 +164,6 @@ class RestrictedUserDto {
|
|
|
145
164
|
## Example Sources
|
|
146
165
|
|
|
147
166
|
- `packages/validation/src/validation.test.ts`
|
|
167
|
+
- `packages/validation/src/mapped-types.test.ts`
|
|
148
168
|
- `examples/realworld-api/src/users/create-user.dto.ts`
|
|
149
169
|
- `examples/auth-jwt-passport/src/auth/login.dto.ts`
|
package/dist/decorators.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAEjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gCAAgC,EAErC,KAAK,0BAA0B,EAChC,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAgE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG/H,KAAK,gBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAClF,KAAK,gBAAgB,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,0BAA0B,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC;AAElH,KAAK,kBAAkB,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"}
|
package/dist/decorators.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ensureMetadataSymbol } from '@fluojs/core/internal';
|
|
2
2
|
import { createClassValidatorFromStandardSchema, isStandardSchemaLike } from './standard-schema.js';
|
|
3
3
|
const standardDtoValidationMetadataKey = Symbol.for('fluo.standard.dto-validation');
|
|
4
4
|
const standardClassValidationMetadataKey = Symbol.for('fluo.standard.class-validation');
|
|
5
|
+
ensureMetadataSymbol();
|
|
5
6
|
function getStandardMetadataBag(metadata) {
|
|
6
7
|
if (metadata === null || metadata === undefined) {
|
|
7
8
|
throw new Error('Decorator metadata is not available. Ensure your environment supports TC39 decorator metadata (Stage 3).');
|
|
8
9
|
}
|
|
9
|
-
void metadataSymbol;
|
|
10
10
|
return metadata;
|
|
11
11
|
}
|
|
12
12
|
function getStandardDtoValidationMap(metadata) {
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EAEZ,MAAM,cAAc,CAAC;AAatB,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EAEZ,MAAM,cAAc,CAAC;AAatB,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,YAAY,CAAC;AAsyB7D;;GAEG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IAC1C,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5D,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAYzE"}
|
package/dist/validation.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import validator from 'validator';
|
|
2
2
|
import { getClassValidationRules, getDtoBindingSchema, getDtoValidationSchema } from '@fluojs/core/internal';
|
|
3
3
|
import { DtoValidationError } from './errors.js';
|
|
4
|
+
function isClassConstructor(dto) {
|
|
5
|
+
return typeof dto === 'function' && Function.prototype.toString.call(dto).startsWith('class ');
|
|
6
|
+
}
|
|
4
7
|
function resolveNestedDto(dto) {
|
|
5
|
-
if (
|
|
8
|
+
if (isClassConstructor(dto)) {
|
|
6
9
|
return dto;
|
|
7
10
|
}
|
|
8
11
|
return dto();
|
|
@@ -283,7 +286,7 @@ const RULE_HANDLERS = {
|
|
|
283
286
|
validatorjs: {
|
|
284
287
|
defaultCode: 'INVALID_FIELD',
|
|
285
288
|
describe: field => `${field} is invalid.`,
|
|
286
|
-
validate: (rule, value) =>
|
|
289
|
+
validate: (rule, value) => runValidatorJs(rule, value)
|
|
287
290
|
},
|
|
288
291
|
arrayContains: {
|
|
289
292
|
defaultCode: 'ARRAY_CONTAINS',
|
|
@@ -399,6 +402,15 @@ function describeValidator(rule, field) {
|
|
|
399
402
|
};
|
|
400
403
|
}
|
|
401
404
|
function runValidatorJs(rule, value) {
|
|
405
|
+
if (rule.validator === 'latitude') {
|
|
406
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= -90 && value <= 90;
|
|
407
|
+
}
|
|
408
|
+
if (rule.validator === 'longitude') {
|
|
409
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= -180 && value <= 180;
|
|
410
|
+
}
|
|
411
|
+
if (typeof value !== 'string') {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
402
414
|
switch (rule.validator) {
|
|
403
415
|
case 'alpha':
|
|
404
416
|
return validator.isAlpha(value);
|
|
@@ -470,16 +482,6 @@ function runValidatorJs(rule, value) {
|
|
|
470
482
|
return validator.isUUID(value, rule.args?.[0]);
|
|
471
483
|
case 'iso8601':
|
|
472
484
|
return validator.isISO8601(value);
|
|
473
|
-
case 'latitude':
|
|
474
|
-
{
|
|
475
|
-
const number = Number(value);
|
|
476
|
-
return !Number.isNaN(number) && number >= -90 && number <= 90;
|
|
477
|
-
}
|
|
478
|
-
case 'longitude':
|
|
479
|
-
{
|
|
480
|
-
const number = Number(value);
|
|
481
|
-
return !Number.isNaN(number) && number >= -180 && number <= 180;
|
|
482
|
-
}
|
|
483
485
|
case 'latLong':
|
|
484
486
|
return validator.isLatLong(value);
|
|
485
487
|
default:
|
|
@@ -500,6 +502,12 @@ function buildInvalidRootIssue() {
|
|
|
500
502
|
message: 'DTO root value must be a plain object.'
|
|
501
503
|
};
|
|
502
504
|
}
|
|
505
|
+
function assertValidRootValue(value, target) {
|
|
506
|
+
if (value instanceof target || isPlainObject(value)) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
throw new DtoValidationError('Validation failed.', [buildInvalidRootIssue()]);
|
|
510
|
+
}
|
|
503
511
|
function getRuleValues(value) {
|
|
504
512
|
return getIterableValues(value) ?? [value];
|
|
505
513
|
}
|
|
@@ -646,14 +654,13 @@ async function collectValidationIssuesInternal(target, value, context, traversal
|
|
|
646
654
|
*/
|
|
647
655
|
export class DefaultValidator {
|
|
648
656
|
async validate(value, target) {
|
|
657
|
+
assertValidRootValue(value, target);
|
|
649
658
|
const issues = await collectValidationIssues(target, value);
|
|
650
659
|
if (issues.length === 0) return;
|
|
651
660
|
throw new DtoValidationError('Validation failed.', issues);
|
|
652
661
|
}
|
|
653
662
|
async materialize(value, target) {
|
|
654
|
-
|
|
655
|
-
throw new DtoValidationError('Validation failed.', [buildInvalidRootIssue()]);
|
|
656
|
-
}
|
|
663
|
+
assertValidRootValue(value, target);
|
|
657
664
|
const instance = createNestedDtoInstance(target, value, {
|
|
658
665
|
active: new WeakSet()
|
|
659
666
|
});
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"decorators",
|
|
10
10
|
"schema"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.4",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@standard-schema/spec": "^1.1.0",
|
|
44
44
|
"validator": "^13.15.26",
|
|
45
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
45
|
+
"@fluojs/core": "^1.0.0-beta.5"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/validator": "^13.15.10",
|