schema-dsl 1.0.0 → 1.0.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/CHANGELOG.md +263 -529
- package/README.md +814 -896
- package/STATUS.md +135 -2
- package/docs/INDEX.md +1 -2
- package/docs/api-reference.md +1 -292
- package/docs/custom-extensions-guide.md +411 -0
- package/docs/enum.md +475 -0
- package/docs/i18n.md +394 -0
- package/docs/performance-benchmark-report.md +179 -0
- package/docs/plugin-system.md +8 -8
- package/docs/typescript-guide.md +554 -0
- package/docs/validate-async.md +1 -1
- package/docs/validation-rules-v1.0.2.md +1601 -0
- package/examples/README.md +81 -0
- package/examples/enum.examples.js +324 -0
- package/examples/express-integration.js +54 -54
- package/examples/i18n-full-demo.js +15 -24
- package/examples/schema-utils-chaining.examples.js +2 -2
- package/examples/slug.examples.js +179 -0
- package/index.d.ts +246 -17
- package/index.js +30 -34
- package/lib/config/constants.js +1 -1
- package/lib/config/patterns/common.js +47 -0
- package/lib/config/patterns/index.js +2 -1
- package/lib/core/DslBuilder.js +500 -8
- package/lib/core/StringExtensions.js +31 -0
- package/lib/core/Validator.js +42 -15
- package/lib/errors/ValidationError.js +3 -3
- package/lib/locales/en-US.js +79 -19
- package/lib/locales/es-ES.js +60 -19
- package/lib/locales/fr-FR.js +84 -43
- package/lib/locales/ja-JP.js +83 -42
- package/lib/locales/zh-CN.js +32 -0
- package/lib/validators/CustomKeywords.js +405 -0
- package/package.json +1 -1
- package/.github/CODE_OF_CONDUCT.md +0 -45
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -57
- package/.github/ISSUE_TEMPLATE/config.yml +0 -11
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -45
- package/.github/ISSUE_TEMPLATE/question.md +0 -31
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -70
- package/.github/SECURITY.md +0 -184
- package/.github/workflows/ci.yml +0 -33
- package/plugins/custom-format.js +0 -101
- package/plugins/custom-validator.js +0 -200
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# schema-dsl 示例代码
|
|
2
|
+
|
|
3
|
+
本目录包含 schema-dsl 的各种功能示例。
|
|
4
|
+
|
|
5
|
+
## 📂 示例列表
|
|
6
|
+
|
|
7
|
+
### 基础示例
|
|
8
|
+
- [simple-example.js](simple-example.js) - 简单入门示例
|
|
9
|
+
- [dsl-style.js](dsl-style.js) - DSL 风格完整示例
|
|
10
|
+
- [enum.examples.js](enum.examples.js) - 枚举类型验证示例
|
|
11
|
+
|
|
12
|
+
### 扩展功能
|
|
13
|
+
- [string-extensions.js](string-extensions.js) - String 扩展功能(username、password、phone)
|
|
14
|
+
- [custom-extension.js](custom-extension.js) - 自定义扩展示例
|
|
15
|
+
- [array-dsl-example.js](array-dsl-example.js) - 数组 DSL 语法示例
|
|
16
|
+
|
|
17
|
+
### 数据库导出
|
|
18
|
+
- [export-demo.js](export-demo.js) - 导出为 MongoDB/MySQL/PostgreSQL Schema
|
|
19
|
+
|
|
20
|
+
### 国际化 (i18n)
|
|
21
|
+
- [dynamic-locale-configuration.js](dynamic-locale-configuration.js) - 动态语言配置示例
|
|
22
|
+
- [dynamic-locale-example.js](dynamic-locale-example.js) - 动态语言切换示例
|
|
23
|
+
- [i18n-full-demo.js](i18n-full-demo.js) - 完整国际化示例
|
|
24
|
+
- [i18n-memory-safety.examples.js](i18n-memory-safety.examples.js) - i18n 内存安全示例
|
|
25
|
+
|
|
26
|
+
### Schema 工具
|
|
27
|
+
- [schema-utils-chaining.examples.js](schema-utils-chaining.examples.js) - SchemaUtils 链式调用示例
|
|
28
|
+
- [dsl-match-example.js](dsl-match-example.js) - 条件验证 match 示例
|
|
29
|
+
- [new-features-comparison.js](new-features-comparison.js) - 新版本功能对比
|
|
30
|
+
|
|
31
|
+
### 插件系统
|
|
32
|
+
- [plugin-system.examples.js](plugin-system.examples.js) - 插件系统完整示例
|
|
33
|
+
|
|
34
|
+
### 实际应用
|
|
35
|
+
- [express-integration.js](express-integration.js) - Express 集成完整示例
|
|
36
|
+
- [middleware-usage.js](middleware-usage.js) - 中间件使用示例
|
|
37
|
+
- [user-registration/](user-registration/) - 用户注册流程完整示例
|
|
38
|
+
- [schema.js](user-registration/schema.js) - Schema 定义
|
|
39
|
+
- [routes.js](user-registration/routes.js) - 路由定义
|
|
40
|
+
- [server.js](user-registration/server.js) - 服务器入口
|
|
41
|
+
- [password-reset/](password-reset/) - 密码重置流程示例
|
|
42
|
+
- [schema.js](password-reset/schema.js) - Schema 定义
|
|
43
|
+
- [test.js](password-reset/test.js) - 测试用例
|
|
44
|
+
|
|
45
|
+
## 🚀 快速开始
|
|
46
|
+
|
|
47
|
+
### 1. 安装依赖
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cd schema-dsl
|
|
51
|
+
npm install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. 运行示例
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 运行简单示例
|
|
58
|
+
node examples/simple-example.js
|
|
59
|
+
|
|
60
|
+
# 运行 String 扩展示例
|
|
61
|
+
node examples/string-extensions.js
|
|
62
|
+
|
|
63
|
+
# 运行导出示例
|
|
64
|
+
node examples/export-demo.js
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. 查看完整文档
|
|
68
|
+
|
|
69
|
+
访问 [docs/INDEX.md](../docs/INDEX.md) 查看完整文档索引。
|
|
70
|
+
|
|
71
|
+
## 📖 相关文档
|
|
72
|
+
|
|
73
|
+
- [DSL 语法文档](../docs/dsl-syntax.md)
|
|
74
|
+
- [String 扩展文档](../docs/string-extensions.md)
|
|
75
|
+
- [插件系统文档](../docs/plugin-system.md)
|
|
76
|
+
- [API 参考](../docs/api-reference.md)
|
|
77
|
+
- [最佳实践](../docs/best-practices.md)
|
|
78
|
+
|
|
79
|
+
## 🤝 贡献
|
|
80
|
+
|
|
81
|
+
欢迎提交新的示例!请参考 [CONTRIBUTING.md](../CONTRIBUTING.md)。
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 枚举功能完整示例
|
|
3
|
+
*
|
|
4
|
+
* 演示 schema-dsl 中枚举的各种用法
|
|
5
|
+
*
|
|
6
|
+
* @since v1.1.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { dsl, validate } = require('../index');
|
|
10
|
+
|
|
11
|
+
console.log('========== 枚举功能示例 ==========\n');
|
|
12
|
+
|
|
13
|
+
// ========================================
|
|
14
|
+
// 1. 基础字符串枚举
|
|
15
|
+
// ========================================
|
|
16
|
+
console.log('【示例 1】基础字符串枚举\n');
|
|
17
|
+
|
|
18
|
+
const userSchema = dsl({
|
|
19
|
+
username: 'string:3-32!',
|
|
20
|
+
role: 'admin|user|guest!', // 简写形式
|
|
21
|
+
status: 'enum:active|inactive|pending' // 完整形式
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let result = validate(userSchema, {
|
|
25
|
+
username: 'john_doe',
|
|
26
|
+
role: 'admin',
|
|
27
|
+
status: 'active'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log('✅ 验证通过:', result.valid);
|
|
31
|
+
console.log('数据:', result.data);
|
|
32
|
+
console.log('');
|
|
33
|
+
|
|
34
|
+
// 测试无效值
|
|
35
|
+
result = validate(userSchema, {
|
|
36
|
+
username: 'jane',
|
|
37
|
+
role: 'superadmin', // 无效值
|
|
38
|
+
status: 'active'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log('❌ 验证失败:', !result.valid);
|
|
42
|
+
console.log('错误:', result.errors[0].message);
|
|
43
|
+
console.log('');
|
|
44
|
+
|
|
45
|
+
// ========================================
|
|
46
|
+
// 2. 布尔值枚举
|
|
47
|
+
// ========================================
|
|
48
|
+
console.log('【示例 2】布尔值枚举\n');
|
|
49
|
+
|
|
50
|
+
const featureSchema = dsl({
|
|
51
|
+
name: 'string!',
|
|
52
|
+
isEnabled: 'true|false!', // 自动识别为布尔值
|
|
53
|
+
isPublic: 'enum:boolean:true|false', // 显式指定布尔值
|
|
54
|
+
verified: 'true|false' // 可选的布尔值
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
result = validate(featureSchema, {
|
|
58
|
+
name: 'dark-mode',
|
|
59
|
+
isEnabled: true,
|
|
60
|
+
isPublic: false
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
console.log('✅ 验证通过:', result.valid);
|
|
64
|
+
console.log('数据:', result.data);
|
|
65
|
+
console.log('');
|
|
66
|
+
|
|
67
|
+
// 测试类型错误
|
|
68
|
+
result = validate(featureSchema, {
|
|
69
|
+
name: 'feature-x',
|
|
70
|
+
isEnabled: 'true', // 字符串 'true' 不是布尔值
|
|
71
|
+
isPublic: false
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log('❌ 类型错误(字符串 vs 布尔值):', !result.valid);
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
// ========================================
|
|
78
|
+
// 3. 数字枚举
|
|
79
|
+
// ========================================
|
|
80
|
+
console.log('【示例 3】数字枚举\n');
|
|
81
|
+
|
|
82
|
+
const taskSchema = dsl({
|
|
83
|
+
title: 'string!',
|
|
84
|
+
priority: '1|2|3!', // 自动识别为数字
|
|
85
|
+
level: 'enum:number:1|2|3|4|5', // 显式指定数字
|
|
86
|
+
rating: '1.0|1.5|2.0|2.5|3.0' // 小数枚举
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
result = validate(taskSchema, {
|
|
90
|
+
title: 'Fix bug',
|
|
91
|
+
priority: 1,
|
|
92
|
+
level: 3,
|
|
93
|
+
rating: 2.5
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log('✅ 验证通过:', result.valid);
|
|
97
|
+
console.log('数据:', result.data);
|
|
98
|
+
console.log('');
|
|
99
|
+
|
|
100
|
+
// 测试超出范围
|
|
101
|
+
result = validate(taskSchema, {
|
|
102
|
+
title: 'New task',
|
|
103
|
+
priority: 4, // 超出 1|2|3
|
|
104
|
+
level: 2
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log('❌ 超出范围:', !result.valid);
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
// ========================================
|
|
111
|
+
// 4. 整数枚举
|
|
112
|
+
// ========================================
|
|
113
|
+
console.log('【示例 4】整数枚举(禁止小数)\n');
|
|
114
|
+
|
|
115
|
+
const gameSchema = dsl({
|
|
116
|
+
playerName: 'string!',
|
|
117
|
+
difficulty: 'enum:integer:1|2|3' // 必须是整数
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
result = validate(gameSchema, {
|
|
121
|
+
playerName: 'Alice',
|
|
122
|
+
difficulty: 2
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
console.log('✅ 整数验证通过:', result.valid);
|
|
126
|
+
console.log('');
|
|
127
|
+
|
|
128
|
+
// 小数应该失败
|
|
129
|
+
result = validate(gameSchema, {
|
|
130
|
+
playerName: 'Bob',
|
|
131
|
+
difficulty: 1.5 // 小数不符合 integer 类型
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.log('❌ 小数验证失败:', !result.valid);
|
|
135
|
+
console.log('');
|
|
136
|
+
|
|
137
|
+
// ========================================
|
|
138
|
+
// 5. 枚举与链式 API
|
|
139
|
+
// ========================================
|
|
140
|
+
console.log('【示例 5】枚举与链式 API\n');
|
|
141
|
+
|
|
142
|
+
const postSchema = dsl({
|
|
143
|
+
title: 'string:5-100!',
|
|
144
|
+
status: dsl('draft|published|archived')
|
|
145
|
+
.label('文章状态')
|
|
146
|
+
.messages({
|
|
147
|
+
'enum': '状态必须是: 草稿、已发布或已归档'
|
|
148
|
+
}),
|
|
149
|
+
visibility: dsl('public|private|unlisted!')
|
|
150
|
+
.label('可见性')
|
|
151
|
+
.default('public')
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
result = validate(postSchema, {
|
|
155
|
+
title: 'My First Post',
|
|
156
|
+
status: 'invalid_status',
|
|
157
|
+
visibility: 'public'
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
console.log('❌ 自定义错误消息:', !result.valid);
|
|
161
|
+
console.log('错误:', result.errors[0].message);
|
|
162
|
+
console.log('');
|
|
163
|
+
|
|
164
|
+
// ========================================
|
|
165
|
+
// 6. 数组中的枚举
|
|
166
|
+
// ========================================
|
|
167
|
+
console.log('【示例 6】数组中的枚举\n');
|
|
168
|
+
|
|
169
|
+
const articleSchema = dsl({
|
|
170
|
+
title: 'string!',
|
|
171
|
+
tags: 'array<enum:tech|business|lifestyle>',
|
|
172
|
+
permissions: 'array<enum:read|write|delete>'
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
result = validate(articleSchema, {
|
|
176
|
+
title: 'Tech Article',
|
|
177
|
+
tags: ['tech', 'business'],
|
|
178
|
+
permissions: ['read', 'write']
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
console.log('✅ 数组枚举验证通过:', result.valid);
|
|
182
|
+
console.log('数据:', result.data);
|
|
183
|
+
console.log('');
|
|
184
|
+
|
|
185
|
+
// ========================================
|
|
186
|
+
// 7. 复杂对象中的枚举
|
|
187
|
+
// ========================================
|
|
188
|
+
console.log('【示例 7】复杂对象中的枚举\n');
|
|
189
|
+
|
|
190
|
+
const orderSchema = dsl({
|
|
191
|
+
orderId: 'string!',
|
|
192
|
+
status: 'pending|processing|completed|cancelled!',
|
|
193
|
+
priority: '1|2|3'.default(2),
|
|
194
|
+
payment: {
|
|
195
|
+
method: 'card|paypal|crypto!',
|
|
196
|
+
status: 'pending|success|failed!'
|
|
197
|
+
},
|
|
198
|
+
items: 'array',
|
|
199
|
+
metadata: {
|
|
200
|
+
isUrgent: 'true|false',
|
|
201
|
+
category: 'electronics|clothing|food'
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
result = validate(orderSchema, {
|
|
206
|
+
orderId: 'ORD-12345',
|
|
207
|
+
status: 'processing',
|
|
208
|
+
priority: 1,
|
|
209
|
+
payment: {
|
|
210
|
+
method: 'card',
|
|
211
|
+
status: 'success'
|
|
212
|
+
},
|
|
213
|
+
items: [],
|
|
214
|
+
metadata: {
|
|
215
|
+
isUrgent: true,
|
|
216
|
+
category: 'electronics'
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
console.log('✅ 复杂对象验证通过:', result.valid);
|
|
221
|
+
console.log('数据:', JSON.stringify(result.data, null, 2));
|
|
222
|
+
console.log('');
|
|
223
|
+
|
|
224
|
+
// ========================================
|
|
225
|
+
// 8. 错误处理
|
|
226
|
+
// ========================================
|
|
227
|
+
console.log('【示例 8】错误处理\n');
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// 无效的布尔值枚举
|
|
231
|
+
dsl({ flag: 'enum:boolean:true|false|maybe' });
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.log('❌ 捕获错误:', error.message);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// 无效的数字枚举
|
|
238
|
+
dsl({ value: 'enum:number:1|2|abc' });
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.log('❌ 捕获错误:', error.message);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log('');
|
|
244
|
+
|
|
245
|
+
// ========================================
|
|
246
|
+
// 9. 实际应用场景
|
|
247
|
+
// ========================================
|
|
248
|
+
console.log('【示例 9】实际应用 - 用户管理系统\n');
|
|
249
|
+
|
|
250
|
+
const userManagementSchema = dsl({
|
|
251
|
+
// 基本信息
|
|
252
|
+
userId: 'string!',
|
|
253
|
+
username: 'string:3-32!',
|
|
254
|
+
email: 'email!',
|
|
255
|
+
|
|
256
|
+
// 枚举字段
|
|
257
|
+
role: 'admin|moderator|user|guest!',
|
|
258
|
+
status: 'active|inactive|suspended|banned',
|
|
259
|
+
emailVerified: 'true|false',
|
|
260
|
+
|
|
261
|
+
// 权限等级(数字枚举)
|
|
262
|
+
permissionLevel: '0|1|2|3|4|5'.default(0),
|
|
263
|
+
|
|
264
|
+
// 偏好设置
|
|
265
|
+
preferences: {
|
|
266
|
+
theme: 'light|dark|auto',
|
|
267
|
+
language: 'en|zh-CN|zh-TW|ja|ko',
|
|
268
|
+
notifications: 'all|mentions|none'
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const newUser = {
|
|
273
|
+
userId: 'u_12345',
|
|
274
|
+
username: 'alice',
|
|
275
|
+
email: 'alice@example.com',
|
|
276
|
+
role: 'user',
|
|
277
|
+
status: 'active',
|
|
278
|
+
emailVerified: true,
|
|
279
|
+
permissionLevel: 1,
|
|
280
|
+
preferences: {
|
|
281
|
+
theme: 'dark',
|
|
282
|
+
language: 'zh-CN',
|
|
283
|
+
notifications: 'mentions'
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
result = validate(userManagementSchema, newUser);
|
|
288
|
+
|
|
289
|
+
console.log('✅ 用户数据验证通过:', result.valid);
|
|
290
|
+
console.log('用户数据:', JSON.stringify(result.data, null, 2));
|
|
291
|
+
console.log('');
|
|
292
|
+
|
|
293
|
+
// ========================================
|
|
294
|
+
// 10. 性能测试
|
|
295
|
+
// ========================================
|
|
296
|
+
console.log('【示例 10】性能测试\n');
|
|
297
|
+
|
|
298
|
+
const perfSchema = dsl({
|
|
299
|
+
status: 'active|inactive|pending',
|
|
300
|
+
priority: '1|2|3',
|
|
301
|
+
flag: 'true|false'
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const iterations = 10000;
|
|
305
|
+
const startTime = Date.now();
|
|
306
|
+
|
|
307
|
+
for (let i = 0; i < iterations; i++) {
|
|
308
|
+
validate(perfSchema, {
|
|
309
|
+
status: 'active',
|
|
310
|
+
priority: 2,
|
|
311
|
+
flag: true
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const endTime = Date.now();
|
|
316
|
+
const duration = endTime - startTime;
|
|
317
|
+
const perSecond = Math.round((iterations / duration) * 1000);
|
|
318
|
+
|
|
319
|
+
console.log(`验证 ${iterations} 次耗时: ${duration}ms`);
|
|
320
|
+
console.log(`平均每秒验证: ${perSecond.toLocaleString()} 次`);
|
|
321
|
+
console.log('');
|
|
322
|
+
|
|
323
|
+
console.log('========== 示例结束 ==========');
|
|
324
|
+
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. SchemaUtils 链式调用
|
|
8
8
|
* 4. 完整 CRUD 场景
|
|
9
9
|
*
|
|
10
|
-
* @version
|
|
10
|
+
* @version 1.0.3
|
|
11
11
|
* @date 2025-12-29
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -63,12 +63,12 @@ app.post('/users', async (req, res, next) => {
|
|
|
63
63
|
try {
|
|
64
64
|
console.log('\n[POST /users] 创建用户');
|
|
65
65
|
console.log('请求体:', req.body);
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
// 使用 validateAsync 验证
|
|
68
68
|
const data = await validateAsync(createUserSchema, req.body);
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
console.log('验证通过,数据:', data);
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
// 保存到数据库
|
|
73
73
|
const user = {
|
|
74
74
|
id: String(db.nextId++),
|
|
@@ -76,20 +76,20 @@ app.post('/users', async (req, res, next) => {
|
|
|
76
76
|
createdAt: new Date().toISOString(),
|
|
77
77
|
updatedAt: new Date().toISOString()
|
|
78
78
|
};
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
db.users.push(user);
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
// 返回公开信息
|
|
83
83
|
const { validate } = require('../index');
|
|
84
84
|
const result = validate(publicUserSchema, user);
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
console.log('返回数据:', result.data);
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
res.status(201).json({
|
|
89
89
|
success: true,
|
|
90
90
|
user: result.data
|
|
91
91
|
});
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
} catch (error) {
|
|
94
94
|
next(error);
|
|
95
95
|
}
|
|
@@ -102,17 +102,17 @@ app.post('/users', async (req, res, next) => {
|
|
|
102
102
|
*/
|
|
103
103
|
app.get('/users', (req, res) => {
|
|
104
104
|
console.log('\n[GET /users] 获取所有用户');
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
const { validate } = require('../index');
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
// 对每个用户应用 publicUserSchema
|
|
109
109
|
const publicUsers = db.users.map(user => {
|
|
110
110
|
const result = validate(publicUserSchema, user);
|
|
111
111
|
return result.data;
|
|
112
112
|
});
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
console.log(`返回 ${publicUsers.length} 个用户`);
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
res.json({
|
|
117
117
|
success: true,
|
|
118
118
|
count: publicUsers.length,
|
|
@@ -127,9 +127,9 @@ app.get('/users', (req, res) => {
|
|
|
127
127
|
*/
|
|
128
128
|
app.get('/users/:id', (req, res) => {
|
|
129
129
|
console.log(`\n[GET /users/${req.params.id}] 获取用户`);
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
const user = db.users.find(u => u.id === req.params.id);
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
if (!user) {
|
|
134
134
|
console.log('用户不存在');
|
|
135
135
|
return res.status(404).json({
|
|
@@ -137,13 +137,13 @@ app.get('/users/:id', (req, res) => {
|
|
|
137
137
|
error: '用户不存在'
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
// 使用 clean 模式自动移除敏感字段
|
|
142
142
|
const { validate } = require('../index');
|
|
143
143
|
const result = validate(publicUserSchema, user);
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
console.log('返回数据:', result.data);
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
res.json({
|
|
148
148
|
success: true,
|
|
149
149
|
user: result.data
|
|
@@ -162,9 +162,9 @@ app.patch('/users/:id', async (req, res, next) => {
|
|
|
162
162
|
try {
|
|
163
163
|
console.log(`\n[PATCH /users/${req.params.id}] 部分更新用户`);
|
|
164
164
|
console.log('请求体:', req.body);
|
|
165
|
-
|
|
165
|
+
|
|
166
166
|
const user = db.users.find(u => u.id === req.params.id);
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
if (!user) {
|
|
169
169
|
console.log('用户不存在');
|
|
170
170
|
return res.status(404).json({
|
|
@@ -172,28 +172,28 @@ app.patch('/users/:id', async (req, res, next) => {
|
|
|
172
172
|
error: '用户不存在'
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
// 验证部分数据
|
|
177
177
|
const data = await validateAsync(updateUserSchema, req.body);
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
console.log('验证通过,更新字段:', data);
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
// 更新用户
|
|
182
182
|
Object.assign(user, data, {
|
|
183
183
|
updatedAt: new Date().toISOString()
|
|
184
184
|
});
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
// 返回公开信息
|
|
187
187
|
const { validate } = require('../index');
|
|
188
188
|
const result = validate(publicUserSchema, user);
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
console.log('返回数据:', result.data);
|
|
191
|
-
|
|
191
|
+
|
|
192
192
|
res.json({
|
|
193
193
|
success: true,
|
|
194
194
|
user: result.data
|
|
195
195
|
});
|
|
196
|
-
|
|
196
|
+
|
|
197
197
|
} catch (error) {
|
|
198
198
|
next(error);
|
|
199
199
|
}
|
|
@@ -210,9 +210,9 @@ app.put('/users/:id', async (req, res, next) => {
|
|
|
210
210
|
try {
|
|
211
211
|
console.log(`\n[PUT /users/${req.params.id}] 替换用户`);
|
|
212
212
|
console.log('请求体:', req.body);
|
|
213
|
-
|
|
213
|
+
|
|
214
214
|
const userIndex = db.users.findIndex(u => u.id === req.params.id);
|
|
215
|
-
|
|
215
|
+
|
|
216
216
|
if (userIndex === -1) {
|
|
217
217
|
console.log('用户不存在');
|
|
218
218
|
return res.status(404).json({
|
|
@@ -220,12 +220,12 @@ app.put('/users/:id', async (req, res, next) => {
|
|
|
220
220
|
error: '用户不存在'
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
// 验证完整数据
|
|
225
225
|
const data = await validateAsync(replaceUserSchema, req.body);
|
|
226
|
-
|
|
226
|
+
|
|
227
227
|
console.log('验证通过,替换用户:', data);
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
// 替换用户(保留 id 和 createdAt)
|
|
230
230
|
const oldUser = db.users[userIndex];
|
|
231
231
|
db.users[userIndex] = {
|
|
@@ -234,18 +234,18 @@ app.put('/users/:id', async (req, res, next) => {
|
|
|
234
234
|
createdAt: oldUser.createdAt,
|
|
235
235
|
updatedAt: new Date().toISOString()
|
|
236
236
|
};
|
|
237
|
-
|
|
237
|
+
|
|
238
238
|
// 返回公开信息
|
|
239
239
|
const { validate } = require('../index');
|
|
240
240
|
const result = validate(publicUserSchema, db.users[userIndex]);
|
|
241
|
-
|
|
241
|
+
|
|
242
242
|
console.log('返回数据:', result.data);
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
res.json({
|
|
245
245
|
success: true,
|
|
246
246
|
user: result.data
|
|
247
247
|
});
|
|
248
|
-
|
|
248
|
+
|
|
249
249
|
} catch (error) {
|
|
250
250
|
next(error);
|
|
251
251
|
}
|
|
@@ -256,9 +256,9 @@ app.put('/users/:id', async (req, res, next) => {
|
|
|
256
256
|
*/
|
|
257
257
|
app.delete('/users/:id', (req, res) => {
|
|
258
258
|
console.log(`\n[DELETE /users/${req.params.id}] 删除用户`);
|
|
259
|
-
|
|
259
|
+
|
|
260
260
|
const userIndex = db.users.findIndex(u => u.id === req.params.id);
|
|
261
|
-
|
|
261
|
+
|
|
262
262
|
if (userIndex === -1) {
|
|
263
263
|
console.log('用户不存在');
|
|
264
264
|
return res.status(404).json({
|
|
@@ -266,11 +266,11 @@ app.delete('/users/:id', (req, res) => {
|
|
|
266
266
|
error: '用户不存在'
|
|
267
267
|
});
|
|
268
268
|
}
|
|
269
|
-
|
|
269
|
+
|
|
270
270
|
db.users.splice(userIndex, 1);
|
|
271
|
-
|
|
271
|
+
|
|
272
272
|
console.log('删除成功');
|
|
273
|
-
|
|
273
|
+
|
|
274
274
|
res.json({
|
|
275
275
|
success: true,
|
|
276
276
|
message: '用户已删除'
|
|
@@ -290,10 +290,10 @@ app.use((error, req, res, next) => {
|
|
|
290
290
|
console.log('\n[错误] ValidationError 被捕获');
|
|
291
291
|
console.log('错误数量:', error.getErrorCount());
|
|
292
292
|
console.log('字段错误:', error.getFieldErrors());
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
return res.status(error.statusCode).json(error.toJSON());
|
|
295
295
|
}
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
// 其他错误
|
|
298
298
|
console.error('\n[错误] 服务器错误:', error);
|
|
299
299
|
res.status(500).json({
|
|
@@ -319,7 +319,7 @@ app.listen(PORT, () => {
|
|
|
319
319
|
console.log(` PUT http://localhost:${PORT}/users/:id - 替换用户`);
|
|
320
320
|
console.log(` DELETE http://localhost:${PORT}/users/:id - 删除用户`);
|
|
321
321
|
console.log(`\n========================================\n`);
|
|
322
|
-
|
|
322
|
+
|
|
323
323
|
// 打印测试命令
|
|
324
324
|
printTestCommands();
|
|
325
325
|
});
|
|
@@ -328,46 +328,46 @@ app.listen(PORT, () => {
|
|
|
328
328
|
|
|
329
329
|
function printTestCommands() {
|
|
330
330
|
console.log(`测试命令(使用 curl):\n`);
|
|
331
|
-
|
|
331
|
+
|
|
332
332
|
console.log(`# 1. 创建用户(成功)`);
|
|
333
333
|
console.log(`curl -X POST http://localhost:${PORT}/users \\`);
|
|
334
334
|
console.log(` -H "Content-Type: application/json" \\`);
|
|
335
335
|
console.log(` -d '{"name":"John Doe","email":"john@example.com","password":"password123","age":30,"role":"user"}'\n`);
|
|
336
|
-
|
|
336
|
+
|
|
337
337
|
console.log(`# 2. 创建用户(失败 - 缺少必填字段)`);
|
|
338
338
|
console.log(`curl -X POST http://localhost:${PORT}/users \\`);
|
|
339
339
|
console.log(` -H "Content-Type: application/json" \\`);
|
|
340
340
|
console.log(` -d '{"name":"Jane"}'\n`);
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
console.log(`# 3. 创建用户(失败 - 额外字段被拒绝)`);
|
|
343
343
|
console.log(`curl -X POST http://localhost:${PORT}/users \\`);
|
|
344
344
|
console.log(` -H "Content-Type: application/json" \\`);
|
|
345
345
|
console.log(` -d '{"name":"Bob","email":"bob@example.com","password":"password123","extraField":"not allowed"}'\n`);
|
|
346
|
-
|
|
346
|
+
|
|
347
347
|
console.log(`# 4. 获取所有用户`);
|
|
348
348
|
console.log(`curl http://localhost:${PORT}/users\n`);
|
|
349
|
-
|
|
349
|
+
|
|
350
350
|
console.log(`# 5. 获取单个用户(替换 ID)`);
|
|
351
351
|
console.log(`curl http://localhost:${PORT}/users/1\n`);
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
console.log(`# 6. 部分更新用户(成功 - 只更新 name)`);
|
|
354
354
|
console.log(`curl -X PATCH http://localhost:${PORT}/users/1 \\`);
|
|
355
355
|
console.log(` -H "Content-Type: application/json" \\`);
|
|
356
356
|
console.log(` -d '{"name":"John Updated"}'\n`);
|
|
357
|
-
|
|
357
|
+
|
|
358
358
|
console.log(`# 7. 部分更新用户(成功 - 允许额外字段)`);
|
|
359
359
|
console.log(`curl -X PATCH http://localhost:${PORT}/users/1 \\`);
|
|
360
360
|
console.log(` -H "Content-Type: application/json" \\`);
|
|
361
361
|
console.log(` -d '{"age":31,"extraField":"allowed"}'\n`);
|
|
362
|
-
|
|
362
|
+
|
|
363
363
|
console.log(`# 8. 替换用户(成功 - 必须提供所有必填字段)`);
|
|
364
364
|
console.log(`curl -X PUT http://localhost:${PORT}/users/1 \\`);
|
|
365
365
|
console.log(` -H "Content-Type: application/json" \\`);
|
|
366
366
|
console.log(` -d '{"name":"John Replaced","email":"john.new@example.com","password":"newpassword123","age":32}'\n`);
|
|
367
|
-
|
|
367
|
+
|
|
368
368
|
console.log(`# 9. 删除用户`);
|
|
369
369
|
console.log(`curl -X DELETE http://localhost:${PORT}/users/1\n`);
|
|
370
|
-
|
|
370
|
+
|
|
371
371
|
console.log(`========================================\n`);
|
|
372
372
|
}
|
|
373
373
|
|