@fluojs/validation 1.0.0-beta.3 → 1.0.0
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 +13 -0
- package/README.md +15 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +22 -15
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -26,6 +26,7 @@ pnpm add @fluojs/validation
|
|
|
26
26
|
- 컨트롤러나 서비스에서 ad hoc parsing 대신 class 기반 검증 규칙을 쓰고 싶을 때
|
|
27
27
|
- `PickType`, `PartialType`, `IntersectionType` 같은 metadata-preserving mapped DTO helper가 필요할 때
|
|
28
28
|
- `@ValidateClass(...)`로 Zod나 Valibot 같은 Standard Schema validator를 붙이고 싶을 때
|
|
29
|
+
- `@fluojs/i18n/validation`으로 명시적으로 localize할 수 있는 안정적인 validation issue code와 path가 필요할 때
|
|
29
30
|
|
|
30
31
|
## 빠른 시작
|
|
31
32
|
|
|
@@ -64,10 +65,17 @@ try {
|
|
|
64
65
|
- **`materialize<T>(value, target)`**: **입력 처리**에 가장 적합합니다. plain 객체를 받아 대상 클래스의 인스턴스를 생성하고, 값을 복사하며, 중첩된 DTO를 재귀적으로 처리한 후 모든 검증 규칙을 실행합니다.
|
|
65
66
|
- **`validate(instance, target)`**: **기존 루트 객체 확인**에 적합합니다. 이미 생성된 루트 값에 대해 검증 규칙을 실행하며, plain 객체인 `@ValidateNested(...)` 값은 중첩 DTO 규칙을 실행하기 위해 임시로 실체화할 수 있습니다. 이 임시 실체화는 호출자가 넘긴 속성 값을 대체하지 않습니다.
|
|
66
67
|
|
|
68
|
+
`validate()`는 문자열, 배열, `null`, `undefined` 같은 잘못된 루트 값을 field 또는
|
|
69
|
+
class rule이 실행되기 전에 deterministic `DtoValidationError`로 거부합니다. 이미
|
|
70
|
+
생성된 대상 DTO 인스턴스와 plain 루트 객체는 허용하므로 request-pipeline binder가
|
|
71
|
+
준비한 DTO payload를 scalar coercion 없이 검증할 수 있습니다.
|
|
72
|
+
|
|
67
73
|
`materialize()`는 plain 입력 객체의 안전한 own enumerable 속성을 복사하고,
|
|
68
74
|
DTO 바인딩 메타데이터를 적용한 뒤 `@ValidateNested(...)` 필드를 재귀적으로
|
|
69
75
|
실체화합니다. 어떤 요청 소스를 선택하고 스칼라 값을 변환할지는 transport 또는
|
|
70
76
|
binder가 검증 전에 담당한다는 request-pipeline 계약을 유지합니다.
|
|
77
|
+
선언된 중첩 DTO의 인스턴스인 기존 중첩 값은 그대로 보존하고, plain 중첩 값만
|
|
78
|
+
해당 필드 또는 collection entry 단위로 실체화합니다.
|
|
71
79
|
`materialize()`에 넘기는 루트 값은 plain 객체이거나 대상 DTO 인스턴스여야 합니다.
|
|
72
80
|
문자열, 배열, `null` 같은 잘못된 루트 값은 대상 DTO 생성자나 필드 initializer가
|
|
73
81
|
실행되기 전에 거부됩니다.
|
|
@@ -89,6 +97,8 @@ type ValidationIssue = {
|
|
|
89
97
|
사용합니다. HTTP 바인딩에서 온 규칙은 `source`를 붙이며, standalone validation이나
|
|
90
98
|
Standard Schema 이슈에서는 값이 없을 수 있습니다.
|
|
91
99
|
|
|
100
|
+
`@fluojs/validation`은 항상 기존 human-readable `message` 값을 방출합니다. Localized message가 필요한 애플리케이션은 validation 실패 후 `@fluojs/i18n/validation`에 opt-in할 수 있습니다. 이 통합은 validator 실행을 바꾸거나 이 패키지를 HTTP locale resolution에 결합하지 않고 `source`, `field`, `code`를 translation key로 매핑합니다.
|
|
101
|
+
|
|
92
102
|
### Mapped DTO 헬퍼
|
|
93
103
|
|
|
94
104
|
```ts
|
|
@@ -124,10 +134,12 @@ class RestrictedUserDto {
|
|
|
124
134
|
### 중첩 검증
|
|
125
135
|
|
|
126
136
|
`@ValidateNested(...)`는 객체 필드, 배열, `Set`, `Map`을 지원합니다. 중첩 DTO path는 validation issue에서 dot/index 표기법을 사용하며, cycle은 안전하게 감지되고 shared reference는 허용됩니다.
|
|
137
|
+
중첩 타입을 지연 해석해야 할 때는 DTO 클래스 자체나 `() => ChildDto`, `function resolveChildDto() { return ChildDto; }` 같은 lazy constructor factory를 전달할 수 있습니다.
|
|
127
138
|
|
|
128
139
|
### 암묵적 scalar coercion 없음
|
|
129
140
|
|
|
130
141
|
`materialize()`는 의도적으로 엄격합니다. Transport가 `'42'`를 넘기고 DTO가 `number`를 기대한다면, transport나 binding layer가 먼저 변환해야 합니다.
|
|
142
|
+
`@IsLatitude()`, `@IsLongitude()`를 포함한 numeric validator는 numeric string을 이미 변환된 number처럼 취급하지 않고 DTO의 numeric 값을 검증합니다.
|
|
131
143
|
|
|
132
144
|
## 공개 API
|
|
133
145
|
|
|
@@ -145,6 +157,7 @@ class RestrictedUserDto {
|
|
|
145
157
|
## 관련 패키지
|
|
146
158
|
|
|
147
159
|
- `@fluojs/http`: request data를 bind한 뒤 이 패키지로 검증합니다.
|
|
160
|
+
- `@fluojs/i18n`: opt-in localized validation issue message를 위한 `@fluojs/i18n/validation`을 제공합니다.
|
|
148
161
|
- `@fluojs/serialization`: response side에서 output DTO를 가공합니다.
|
|
149
162
|
- `@fluojs/core`: validation decorator가 사용하는 metadata primitive를 제공합니다.
|
|
150
163
|
|
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ pnpm add @fluojs/validation
|
|
|
26
26
|
- when you want class-based validation rules instead of ad hoc parsing in controllers and services
|
|
27
27
|
- when you need metadata-preserving mapped DTO helpers such as `PickType`, `PartialType`, and `IntersectionType`
|
|
28
28
|
- when you want to attach Standard Schema validators such as Zod or Valibot through `@ValidateClass(...)`
|
|
29
|
+
- when you want stable validation issue codes and paths that can be localized explicitly through `@fluojs/i18n/validation`
|
|
29
30
|
|
|
30
31
|
## Quick Start
|
|
31
32
|
|
|
@@ -66,10 +67,19 @@ try {
|
|
|
66
67
|
temporarily materialize plain nested `@ValidateNested(...)` values to run their
|
|
67
68
|
nested DTO rules without replacing the caller's properties
|
|
68
69
|
|
|
70
|
+
`validate()` rejects malformed roots such as strings, arrays, `null`, and
|
|
71
|
+
`undefined` with a deterministic `DtoValidationError` before field or class rules
|
|
72
|
+
run. It accepts already-created target DTO instances and plain root objects so
|
|
73
|
+
request-pipeline binders can validate their prepared DTO payloads without scalar
|
|
74
|
+
coercion.
|
|
75
|
+
|
|
69
76
|
`materialize()` copies safe own enumerable properties from plain input objects,
|
|
70
77
|
applies DTO binding metadata, and recursively hydrates `@ValidateNested(...)`
|
|
71
78
|
fields. It preserves the request-pipeline contract that transports or binders own
|
|
72
79
|
source selection and scalar conversion before validation runs.
|
|
80
|
+
Existing nested values that are already instances of the declared nested DTO are
|
|
81
|
+
preserved; plain nested values are hydrated only for the affected nested field or
|
|
82
|
+
collection entry.
|
|
73
83
|
The root value passed to `materialize()` must already be a plain object or an
|
|
74
84
|
instance of the target DTO; malformed roots such as strings, arrays, and `null`
|
|
75
85
|
are rejected before the target DTO constructor or field initializers run.
|
|
@@ -91,6 +101,8 @@ Nested DTOs use dot paths and collection indexes, such as `address.city` or
|
|
|
91
101
|
`items[0].name`. HTTP bindings attach `source` when the rule came from request
|
|
92
102
|
metadata; standalone validation and Standard Schema issues may leave it unset.
|
|
93
103
|
|
|
104
|
+
`@fluojs/validation` always emits its normal human-readable `message` values. Applications that need localized messages can opt into `@fluojs/i18n/validation` after validation fails. That integration maps `source`, `field`, and `code` to translation keys without changing validator execution or coupling this package to HTTP locale resolution.
|
|
105
|
+
|
|
94
106
|
### Mapped DTO helpers
|
|
95
107
|
|
|
96
108
|
```ts
|
|
@@ -126,10 +138,12 @@ class RestrictedUserDto {
|
|
|
126
138
|
### Nested validation
|
|
127
139
|
|
|
128
140
|
`@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.
|
|
141
|
+
Pass either a DTO class or a lazy constructor factory such as `() => ChildDto` or `function resolveChildDto() { return ChildDto; }` when nested types need deferred resolution.
|
|
129
142
|
|
|
130
143
|
### No implicit scalar coercion
|
|
131
144
|
|
|
132
145
|
`materialize()` is intentionally strict. If a transport gives you `'42'` and your DTO expects `number`, the transport or binding layer must convert it first.
|
|
146
|
+
Numeric validators, including `@IsLatitude()` and `@IsLongitude()`, validate numeric DTO values without treating numeric strings as already-converted numbers.
|
|
133
147
|
|
|
134
148
|
## Public API
|
|
135
149
|
|
|
@@ -147,6 +161,7 @@ class RestrictedUserDto {
|
|
|
147
161
|
## Related Packages
|
|
148
162
|
|
|
149
163
|
- `@fluojs/http`: binds request data, then uses this package to validate it
|
|
164
|
+
- `@fluojs/i18n`: exposes `@fluojs/i18n/validation` for opt-in localized validation issue messages
|
|
150
165
|
- `@fluojs/serialization`: shapes output DTOs on the response side
|
|
151
166
|
- `@fluojs/core`: provides the metadata primitives used by validation decorators
|
|
152
167
|
|
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
|
|
12
|
+
"version": "1.0.0",
|
|
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
|
|
45
|
+
"@fluojs/core": "^1.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/validator": "^13.15.10",
|