@qlover/create-app 0.7.13 → 0.7.15
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/CHANGELOG.md +89 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/README.en.md +131 -0
- package/dist/templates/next-app/README.md +115 -20
- package/dist/templates/next-app/config/IOCIdentifier.ts +14 -1
- package/dist/templates/next-app/config/Identifier/index.ts +1 -0
- package/dist/templates/next-app/config/Identifier/page.admin.ts +48 -0
- package/dist/templates/next-app/config/i18n/admin18n.ts +33 -0
- package/dist/templates/next-app/config/i18n/index.ts +3 -1
- package/dist/templates/next-app/docs/en/api.md +387 -0
- package/dist/templates/next-app/docs/en/component.md +544 -0
- package/dist/templates/next-app/docs/en/database.md +496 -0
- package/dist/templates/next-app/docs/en/development-guide.md +727 -0
- package/dist/templates/next-app/docs/en/env.md +563 -0
- package/dist/templates/next-app/docs/en/i18n.md +287 -0
- package/dist/templates/next-app/docs/en/index.md +166 -0
- package/dist/templates/next-app/docs/en/page.md +457 -0
- package/dist/templates/next-app/docs/en/project-structure.md +177 -0
- package/dist/templates/next-app/docs/en/router.md +427 -0
- package/dist/templates/next-app/docs/en/theme.md +532 -0
- package/dist/templates/next-app/docs/en/validator.md +478 -0
- package/dist/templates/next-app/docs/zh/api.md +387 -0
- package/dist/templates/next-app/docs/zh/component.md +544 -0
- package/dist/templates/next-app/docs/zh/database.md +496 -0
- package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
- package/dist/templates/next-app/docs/zh/env.md +563 -0
- package/dist/templates/next-app/docs/zh/i18n.md +287 -0
- package/dist/templates/next-app/docs/zh/index.md +166 -0
- package/dist/templates/next-app/docs/zh/page.md +457 -0
- package/dist/templates/next-app/docs/zh/project-structure.md +177 -0
- package/dist/templates/next-app/docs/zh/router.md +427 -0
- package/dist/templates/next-app/docs/zh/theme.md +532 -0
- package/dist/templates/next-app/docs/zh/validator.md +476 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +2 -2
- package/dist/templates/next-app/next.config.ts +1 -1
- package/dist/templates/next-app/package.json +3 -1
- package/dist/templates/next-app/public/locales/en.json +8 -1
- package/dist/templates/next-app/public/locales/zh.json +8 -1
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +14 -16
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +10 -3
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/page.tsx +2 -3
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
- package/dist/templates/next-app/src/app/api/ai/completions/route.ts +32 -0
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +3 -0
- package/dist/templates/next-app/src/base/cases/ChatAction.ts +21 -0
- package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +36 -0
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +1 -3
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +1 -1
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +1 -1
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +23 -1
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +2 -2
- package/dist/templates/next-app/src/base/types/PageProps.ts +1 -1
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +1 -0
- package/dist/templates/next-app/src/core/globals.ts +2 -0
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +4 -1
- package/dist/templates/next-app/src/{base/cases → server}/PageParams.ts +1 -1
- package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +31 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +1 -1
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +6 -4
- package/dist/templates/next-app/src/server/services/AIService.ts +43 -0
- package/dist/templates/next-app/src/server/services/ApiUserService.ts +1 -1
- package/dist/templates/next-app/src/server/{SupabaseBridge.ts → sqlBridges/SupabaseBridge.ts} +16 -11
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +4 -4
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +1 -1
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +32 -25
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +12 -26
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +37 -5
- package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +17 -0
- package/dist/templates/next-app/src/uikit/components/ClientSeo.tsx +36 -0
- package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +5 -6
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +2 -0
- package/dist/templates/next-app/src/uikit/components/With.tsx +17 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +30 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +65 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +59 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +28 -0
- package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +19 -0
- package/package.json +1 -1
- package/dist/templates/next-app/docs/env.md +0 -94
- package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +0 -21
- package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +0 -92
- package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +0 -3
- package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +0 -6
- package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +0 -43
- package/dist/templates/next-app/config/i18n/{HomeI18n .ts → HomeI18n.ts} +0 -0
- package/dist/templates/next-app/{build → make}/generateLocales.ts +2 -2
- /package/dist/templates/next-app/src/{base → server}/port/PaginationInterface.ts +0 -0
- /package/dist/templates/next-app/src/{base → server}/port/ParamsHandlerInterface.ts +0 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# Validator Development Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Validator Architecture Overview](#validator-architecture-overview)
|
|
6
|
+
2. [Validator Interfaces and Abstraction Layer](#validator-interfaces-and-abstraction-layer)
|
|
7
|
+
3. [Concrete Validator Implementation](#concrete-validator-implementation)
|
|
8
|
+
4. [Validator Usage Examples](#validator-usage-examples)
|
|
9
|
+
5. [Custom Validator Examples](#custom-validator-examples)
|
|
10
|
+
6. [Best Practices and Extensions](#best-practices-and-extensions)
|
|
11
|
+
|
|
12
|
+
## Validator Architecture Overview
|
|
13
|
+
|
|
14
|
+
### 1. Overall Architecture
|
|
15
|
+
|
|
16
|
+
The project adopts a layered validator architecture design:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Application Layer Validation Layer
|
|
20
|
+
┌──────────────┐ ┌──────────────┐
|
|
21
|
+
│Business Service│ │Validator Interface│
|
|
22
|
+
├──────────────┤ ├──────────────┤
|
|
23
|
+
│Param Processing│ ◄─────┤Validator Implement│
|
|
24
|
+
├──────────────┤ ├──────────────┤
|
|
25
|
+
│Error Handling │ │Validation Rules│
|
|
26
|
+
└──────────────┘ └──────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Core Components
|
|
30
|
+
|
|
31
|
+
- **Validator Interface**: `ValidatorInterface`
|
|
32
|
+
- **Validator Implementation**: `LoginValidator`, `PaginationValidator`, etc.
|
|
33
|
+
- **Validation Rules**: Validation rules defined using `zod` library
|
|
34
|
+
- **Error Handling**: Unified error response format
|
|
35
|
+
|
|
36
|
+
### 3. Validation Result Types
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Validation failure result
|
|
40
|
+
interface ValidationFaildResult {
|
|
41
|
+
path: PropertyKey[]; // Path of failed validation field
|
|
42
|
+
message: string; // Error message
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validator interface
|
|
46
|
+
interface ValidatorInterface {
|
|
47
|
+
// Validate data and return result
|
|
48
|
+
validate(
|
|
49
|
+
data: unknown
|
|
50
|
+
): Promise<void | ValidationFaildResult> | void | ValidationFaildResult;
|
|
51
|
+
|
|
52
|
+
// Validate data, return data if successful, throw error if failed
|
|
53
|
+
getThrow(data: unknown): unknown;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Validator Interfaces and Abstraction Layer
|
|
58
|
+
|
|
59
|
+
### 1. Base Validator Interface
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
export interface ValidatorInterface {
|
|
63
|
+
/**
|
|
64
|
+
* Validate data and return result
|
|
65
|
+
* @param data - Data to validate
|
|
66
|
+
* @returns void if validation passes, otherwise returns validation error
|
|
67
|
+
*/
|
|
68
|
+
validate(
|
|
69
|
+
data: unknown
|
|
70
|
+
): Promise<void | ValidationFaildResult> | void | ValidationFaildResult;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate data, return data if successful, throw error if failed
|
|
74
|
+
* @param data - Data to validate
|
|
75
|
+
* @returns Validated data
|
|
76
|
+
* @throws {ExecutorError} If data is invalid, includes validation error details
|
|
77
|
+
*/
|
|
78
|
+
getThrow(data: unknown): unknown;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Validation Rule Definition
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Define validation rules using zod
|
|
86
|
+
const emailSchema = z.string().email({ message: V_EMAIL_INVALID });
|
|
87
|
+
|
|
88
|
+
const passwordSchema = z
|
|
89
|
+
.string()
|
|
90
|
+
.min(6, { message: V_PASSWORD_MIN_LENGTH })
|
|
91
|
+
.max(50, { message: V_PASSWORD_MAX_LENGTH })
|
|
92
|
+
.regex(/^\S+$/, { message: V_PASSWORD_SPECIAL_CHARS });
|
|
93
|
+
|
|
94
|
+
const pageSchema = z
|
|
95
|
+
.number()
|
|
96
|
+
.or(z.string())
|
|
97
|
+
.transform((val) => Number(val))
|
|
98
|
+
.refine((val) => val > 0, {
|
|
99
|
+
message: API_PAGE_INVALID
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Concrete Validator Implementation
|
|
104
|
+
|
|
105
|
+
### 1. Login Validator
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
@injectable()
|
|
109
|
+
export class LoginValidator implements ValidatorInterface {
|
|
110
|
+
// Validate email
|
|
111
|
+
validateEmail(data: unknown): void | ValidationFaildResult {
|
|
112
|
+
const emailResult = emailSchema.safeParse(data);
|
|
113
|
+
if (!emailResult.success) {
|
|
114
|
+
return emailResult.error.issues[0];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Validate password
|
|
119
|
+
validatePassword(data: unknown): void | ValidationFaildResult {
|
|
120
|
+
const passwordResult = passwordSchema.safeParse(data);
|
|
121
|
+
if (!passwordResult.success) {
|
|
122
|
+
return passwordResult.error.issues[0];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Implement validate interface
|
|
127
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
128
|
+
if (typeof data !== 'object' || data === null) {
|
|
129
|
+
return {
|
|
130
|
+
path: ['form'],
|
|
131
|
+
message: V_LOGIN_PARAMS_REQUIRED
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const { email, password } = data as Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
// Validate email
|
|
138
|
+
let validateResult = this.validateEmail(email);
|
|
139
|
+
if (validateResult != null) {
|
|
140
|
+
return validateResult;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Validate password
|
|
144
|
+
validateResult = this.validatePassword(password);
|
|
145
|
+
if (validateResult != null) {
|
|
146
|
+
return validateResult;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Implement getThrow interface
|
|
151
|
+
getThrow(data: unknown): LoginValidatorData {
|
|
152
|
+
const result = this.validate(data);
|
|
153
|
+
if (result == null) {
|
|
154
|
+
return data as LoginValidatorData;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const error: ExtendedExecutorError = new ExecutorError(result.message);
|
|
158
|
+
error.issues = [result];
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2. Pagination Validator
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
@injectable()
|
|
168
|
+
export class PaginationValidator implements ValidatorInterface {
|
|
169
|
+
protected defaultPageSize = 10;
|
|
170
|
+
|
|
171
|
+
validatePageSize(value: unknown): void | ValidationFaildResult {
|
|
172
|
+
const result = pageSchema.safeParse(value);
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
return result.error.issues[0];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
179
|
+
if (typeof data !== 'object' || data === null) {
|
|
180
|
+
return {
|
|
181
|
+
path: ['form'],
|
|
182
|
+
message: 'form is required'
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return this.validatePageSize((data as Record<string, unknown>).page);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getThrow(data: unknown): PaginationInterface {
|
|
190
|
+
const result = this.validate(data);
|
|
191
|
+
if (result) {
|
|
192
|
+
throw new Error(result.message);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { page: 1, pageSize: this.defaultPageSize };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Validator Usage Examples
|
|
201
|
+
|
|
202
|
+
### 1. Using in API Routes
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
export async function POST(req: NextRequest) {
|
|
206
|
+
const server = new BootstrapServer();
|
|
207
|
+
|
|
208
|
+
const result = await server.execNoError(async ({ parameters: { IOC } }) => {
|
|
209
|
+
// Get request data
|
|
210
|
+
const data = await req.json();
|
|
211
|
+
|
|
212
|
+
// Use validator to validate data
|
|
213
|
+
const loginValidator = IOC(LoginValidator);
|
|
214
|
+
const validatedData = loginValidator.getThrow(data);
|
|
215
|
+
|
|
216
|
+
// Use validated data
|
|
217
|
+
const userService = IOC(UserService);
|
|
218
|
+
return userService.login(validatedData);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (result instanceof ExecutorError) {
|
|
222
|
+
return NextResponse.json(new AppErrorApi(result.id, result.message));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return NextResponse.json(new AppSuccessApi(result));
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 2. Using in Services
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
@injectable()
|
|
233
|
+
export class UserService {
|
|
234
|
+
constructor(
|
|
235
|
+
@inject(LoginValidator) private loginValidator: LoginValidator,
|
|
236
|
+
@inject(UserRepository) private userRepository: UserRepositoryInterface
|
|
237
|
+
) {}
|
|
238
|
+
|
|
239
|
+
async validateAndCreateUser(data: unknown): Promise<User> {
|
|
240
|
+
// Validate data
|
|
241
|
+
const validatedData = this.loginValidator.getThrow(data);
|
|
242
|
+
|
|
243
|
+
// Use validated data
|
|
244
|
+
return this.userRepository.add(validatedData);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Custom Validator Examples
|
|
250
|
+
|
|
251
|
+
### 1. Create Custom Validation Rules
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// Define custom validation rules
|
|
255
|
+
const phoneSchema = z
|
|
256
|
+
.string()
|
|
257
|
+
.regex(/^1[3-9]\d{9}$/, { message: 'Invalid phone number format' });
|
|
258
|
+
|
|
259
|
+
const addressSchema = z.object({
|
|
260
|
+
province: z.string().min(1, 'Province cannot be empty'),
|
|
261
|
+
city: z.string().min(1, 'City cannot be empty'),
|
|
262
|
+
detail: z.string().min(1, 'Detailed address cannot be empty')
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Create user info validator
|
|
266
|
+
@injectable()
|
|
267
|
+
export class UserInfoValidator implements ValidatorInterface {
|
|
268
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
269
|
+
if (typeof data !== 'object' || data === null) {
|
|
270
|
+
return {
|
|
271
|
+
path: ['form'],
|
|
272
|
+
message: 'Form data cannot be empty'
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const { phone, address } = data as Record<string, unknown>;
|
|
277
|
+
|
|
278
|
+
// Validate phone number
|
|
279
|
+
const phoneResult = phoneSchema.safeParse(phone);
|
|
280
|
+
if (!phoneResult.success) {
|
|
281
|
+
return phoneResult.error.issues[0];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Validate address
|
|
285
|
+
const addressResult = addressSchema.safeParse(address);
|
|
286
|
+
if (!addressResult.success) {
|
|
287
|
+
return addressResult.error.issues[0];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getThrow(data: unknown): UserInfoData {
|
|
292
|
+
const result = this.validate(data);
|
|
293
|
+
if (result) {
|
|
294
|
+
throw new ExecutorError(result.message);
|
|
295
|
+
}
|
|
296
|
+
return data as UserInfoData;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 2. Combine Multiple Validators
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
@injectable()
|
|
305
|
+
export class UserProfileValidator implements ValidatorInterface {
|
|
306
|
+
constructor(
|
|
307
|
+
@inject(UserInfoValidator) private userInfoValidator: UserInfoValidator,
|
|
308
|
+
@inject(LoginValidator) private loginValidator: LoginValidator
|
|
309
|
+
) {}
|
|
310
|
+
|
|
311
|
+
async validate(data: unknown): Promise<void | ValidationFaildResult> {
|
|
312
|
+
// Validate login information
|
|
313
|
+
const loginResult = this.loginValidator.validate(data);
|
|
314
|
+
if (loginResult) return loginResult;
|
|
315
|
+
|
|
316
|
+
// Validate user information
|
|
317
|
+
const userInfoResult = this.userInfoValidator.validate(data);
|
|
318
|
+
if (userInfoResult) return userInfoResult;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async getThrow(data: unknown): Promise<UserProfileData> {
|
|
322
|
+
const result = await this.validate(data);
|
|
323
|
+
if (result) {
|
|
324
|
+
throw new ExecutorError(result.message);
|
|
325
|
+
}
|
|
326
|
+
return data as UserProfileData;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Best Practices and Extensions
|
|
332
|
+
|
|
333
|
+
### 1. Validator Organization
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// 1. Organize validators by feature modules
|
|
337
|
+
src / validators / user / LoginValidator.ts;
|
|
338
|
+
ProfileValidator.ts;
|
|
339
|
+
SettingsValidator.ts;
|
|
340
|
+
product / CreateValidator.ts;
|
|
341
|
+
UpdateValidator.ts;
|
|
342
|
+
order / PlaceOrderValidator.ts;
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 2. Validation Rule Reuse
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// 1. Create shared validation rules
|
|
349
|
+
const commonRules = {
|
|
350
|
+
email: z.string().email({ message: 'Invalid email format' }),
|
|
351
|
+
phone: z
|
|
352
|
+
.string()
|
|
353
|
+
.regex(/^1[3-9]\d{9}$/, { message: 'Invalid phone number format' }),
|
|
354
|
+
password: z
|
|
355
|
+
.string()
|
|
356
|
+
.min(6, 'Password must be at least 6 characters')
|
|
357
|
+
.max(50, 'Password must be at most 50 characters')
|
|
358
|
+
.regex(/^\S+$/, 'Password cannot contain spaces')
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// 2. Reuse in validators
|
|
362
|
+
export class UserValidator implements ValidatorInterface {
|
|
363
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
364
|
+
const schema = z.object({
|
|
365
|
+
email: commonRules.email,
|
|
366
|
+
password: commonRules.password,
|
|
367
|
+
phone: commonRules.phone.optional()
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const result = schema.safeParse(data);
|
|
371
|
+
if (!result.success) {
|
|
372
|
+
return result.error.issues[0];
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### 3. Enhanced Error Handling
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// 1. Create validation error base class
|
|
382
|
+
export class ValidationError extends Error {
|
|
383
|
+
constructor(
|
|
384
|
+
public readonly path: PropertyKey[],
|
|
385
|
+
message: string
|
|
386
|
+
) {
|
|
387
|
+
super(message);
|
|
388
|
+
this.name = 'ValidationError';
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 2. Create field validation error
|
|
393
|
+
export class FieldValidationError extends ValidationError {
|
|
394
|
+
constructor(
|
|
395
|
+
public readonly field: string,
|
|
396
|
+
message: string
|
|
397
|
+
) {
|
|
398
|
+
super([field], message);
|
|
399
|
+
this.name = 'FieldValidationError';
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 3. Use in validators
|
|
404
|
+
export class EnhancedValidator implements ValidatorInterface {
|
|
405
|
+
getThrow(data: unknown): unknown {
|
|
406
|
+
const result = this.validate(data);
|
|
407
|
+
if (result) {
|
|
408
|
+
throw new FieldValidationError(result.path.join('.'), result.message);
|
|
409
|
+
}
|
|
410
|
+
return data;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 4. Async Validation Support
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
@injectable()
|
|
419
|
+
export class AsyncValidator implements ValidatorInterface {
|
|
420
|
+
constructor(
|
|
421
|
+
@inject(UserRepository) private userRepository: UserRepositoryInterface
|
|
422
|
+
) {}
|
|
423
|
+
|
|
424
|
+
async validate(data: unknown): Promise<void | ValidationFaildResult> {
|
|
425
|
+
// Basic validation
|
|
426
|
+
const basicResult = this.validateBasicFields(data);
|
|
427
|
+
if (basicResult) return basicResult;
|
|
428
|
+
|
|
429
|
+
// Async validation (e.g., check if email exists)
|
|
430
|
+
const { email } = data as { email: string };
|
|
431
|
+
const existingUser = await this.userRepository.getUserByEmail(email);
|
|
432
|
+
|
|
433
|
+
if (existingUser) {
|
|
434
|
+
return {
|
|
435
|
+
path: ['email'],
|
|
436
|
+
message: 'Email is already registered'
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async getThrow(data: unknown): Promise<unknown> {
|
|
442
|
+
const result = await this.validate(data);
|
|
443
|
+
if (result) {
|
|
444
|
+
throw new ValidationError(result.path, result.message);
|
|
445
|
+
}
|
|
446
|
+
return data;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Summary
|
|
452
|
+
|
|
453
|
+
The project's validator design follows these principles:
|
|
454
|
+
|
|
455
|
+
1. **Interface Abstraction**:
|
|
456
|
+
- Clear validator interface definitions
|
|
457
|
+
- Unified validation result format
|
|
458
|
+
- Extensible validator implementation
|
|
459
|
+
|
|
460
|
+
2. **Type Safety**:
|
|
461
|
+
- Use zod for type-safe validation rules
|
|
462
|
+
- TypeScript type definitions
|
|
463
|
+
- Runtime type checking
|
|
464
|
+
|
|
465
|
+
3. **Error Handling**:
|
|
466
|
+
- Unified error response format
|
|
467
|
+
- Detailed error messages
|
|
468
|
+
- Error chain tracking
|
|
469
|
+
|
|
470
|
+
4. **Extensibility**:
|
|
471
|
+
- Support for custom validation rules
|
|
472
|
+
- Support for validator composition
|
|
473
|
+
- Support for async validation
|
|
474
|
+
|
|
475
|
+
5. **Best Practices**:
|
|
476
|
+
- Validation rule reuse
|
|
477
|
+
- Modular organization
|
|
478
|
+
- Enhanced error handling
|