mm_validate 0.0.0-stage → 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/LICENSE +15 -0
- package/README.md +190 -2
- package/example/index.js +120 -0
- package/index.js +24 -0
- package/lib/validate.js +303 -0
- package/package.json +51 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 qww
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,191 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mm_validate
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个轻量的 Node.js 参数验证模块。按 **JSON Schema 常用子集** 校验参数,校验失败时返回**首个**错误信息,并通过字段路径直接定位出错位置。
|
|
4
|
+
|
|
5
|
+
- 入口:`index.js`(CommonJS)
|
|
6
|
+
- 运行时依赖:无
|
|
7
|
+
- 测试覆盖率:100%(见 `jest.config.js` 阈值)
|
|
8
|
+
|
|
9
|
+
## 特性
|
|
10
|
+
|
|
11
|
+
- **零依赖**:只依赖 Node.js 原生能力,安装体积小。
|
|
12
|
+
- **短路返回**:按关键字顺序校验,命中第一个错误立即返回。
|
|
13
|
+
- **路径提示**:嵌套对象与数组的错误信息自动附带路径,如 `user.age`、`[1].id`。
|
|
14
|
+
- **递归校验**:支持 `properties` 与 `items` 的任意层级嵌套。
|
|
15
|
+
- **结果简单**:通过返回空字符串 `''`,失败返回错误描述,便于直接用于 `if` 判断。
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install mm_validate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 环境要求
|
|
24
|
+
|
|
25
|
+
- Node.js >= 16
|
|
26
|
+
- 模块规范:CommonJS
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const { Validate } = require('mm_validate');
|
|
34
|
+
|
|
35
|
+
const schema = {
|
|
36
|
+
type: 'object',
|
|
37
|
+
required: ['name', 'age'],
|
|
38
|
+
properties: {
|
|
39
|
+
name: { type: 'string', min_length: 2, max_length: 20 },
|
|
40
|
+
age: { type: 'integer', minimum: 0, maximum: 150 },
|
|
41
|
+
role: { type: 'string', enum: ['admin', 'user'] },
|
|
42
|
+
tags: { type: 'array', items: { type: 'string' } }
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const validate = new Validate(schema);
|
|
47
|
+
|
|
48
|
+
console.log(validate.check({ name: '张三', age: 28, role: 'admin', tags: ['a'] }));
|
|
49
|
+
// ''
|
|
50
|
+
|
|
51
|
+
console.log(validate.check({ name: '张三' }));
|
|
52
|
+
// 'age: required missing'
|
|
53
|
+
|
|
54
|
+
console.log(validate.check({ name: '张三', age: 'x' }));
|
|
55
|
+
// 'age: type expected "integer" but got "string"'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
### `new Validate(config = {})`
|
|
61
|
+
|
|
62
|
+
创建一个校验器。
|
|
63
|
+
|
|
64
|
+
| 参数 | 类型 | 说明 |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `config` | `Object` | 校验配置(JSON Schema 常用子集)。传入非对象(如 `null`、数组)时不执行任何校验。 |
|
|
67
|
+
|
|
68
|
+
校验器会按引用保存配置,可在创建后修改 `validate.config` 复用实例。
|
|
69
|
+
|
|
70
|
+
### `check(params)`
|
|
71
|
+
|
|
72
|
+
校验参数。
|
|
73
|
+
|
|
74
|
+
| 参数 | 类型 | 说明 |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `params` | `*` | 待校验的参数。 |
|
|
77
|
+
|
|
78
|
+
| 返回值 | 说明 |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `''` | 校验通过 |
|
|
81
|
+
| `string` | 校验失败,返回首个错误信息(含字段路径) |
|
|
82
|
+
|
|
83
|
+
## 支持的校验关键字
|
|
84
|
+
|
|
85
|
+
| 关键字 | 适用类型 | 说明 | 错误示例 |
|
|
86
|
+
| --- | --- | --- | --- |
|
|
87
|
+
| `type` | 任意 | 数据类型:`null` / `array` / `object` / `string` / `number` / `integer` / `boolean`。`number` 不接受 `NaN`、`Infinity` | `type expected "string" but got "number"` |
|
|
88
|
+
| `enum` | 任意 | 值必须属于枚举数组 | `enum expected ["admin","user"] but got "guest"` |
|
|
89
|
+
| `const` | 任意 | 值必须严格等于指定值 | `const expected 1 but got 2` |
|
|
90
|
+
| `min_length` | `string` | 字符串最小长度 | `min_length expected >= 3 but got 2` |
|
|
91
|
+
| `max_length` | `string` | 字符串最大长度 | `max_length expected <= 1 but got 2` |
|
|
92
|
+
| `minimum` | `number` | 数值下限(包含) | `minimum expected >= 3 but got 2` |
|
|
93
|
+
| `maximum` | `number` | 数值上限(包含) | `maximum expected <= 1 but got 2` |
|
|
94
|
+
| `pattern` | `string` | 正则表达式(字符串形式) | `pattern expected match /^[a-z]+$/ but got "qww123"` |
|
|
95
|
+
| `required` | `object` | 必填字段名数组 | `name: required missing` |
|
|
96
|
+
| `properties` | `object` | 子字段配置,递归校验 | `user.age: type expected "number" but got "string"` |
|
|
97
|
+
| `items` | `array` | 数组元素配置,递归校验 | `[1].id: type expected "number" but got "string"` |
|
|
98
|
+
|
|
99
|
+
校验顺序为:`type` → `enum` → `const` → `min_length` / `max_length` → `minimum` / `maximum` → `pattern` → `required` → `properties` → `items`。
|
|
100
|
+
|
|
101
|
+
未声明或类型不符合要求的关键字会被跳过。例如 `min_length` 只对字符串生效,`required` 只对对象生效。
|
|
102
|
+
|
|
103
|
+
## 错误信息与路径
|
|
104
|
+
|
|
105
|
+
错误信息格式为 `路径: 错误详情`,顶层错误不带路径前缀。
|
|
106
|
+
|
|
107
|
+
| 场景 | 路径示例 |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| 顶层字段 | `age` |
|
|
110
|
+
| 嵌套对象 | `user.age` |
|
|
111
|
+
| 数组元素 | `[1]` |
|
|
112
|
+
| 对象数组元素字段 | `[1].id` |
|
|
113
|
+
| 嵌套数组 | `[1][1]` |
|
|
114
|
+
|
|
115
|
+
## 在接口中校验参数
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
'use strict';
|
|
119
|
+
|
|
120
|
+
const { Validate } = require('mm_validate');
|
|
121
|
+
|
|
122
|
+
const ARTICLE_SCHEMA = {
|
|
123
|
+
type: 'object',
|
|
124
|
+
required: ['title', 'content', 'status'],
|
|
125
|
+
properties: {
|
|
126
|
+
title: { type: 'string', min_length: 2, max_length: 50 },
|
|
127
|
+
content: { type: 'string', min_length: 1 },
|
|
128
|
+
status: { type: 'string', enum: ['draft', 'published'] },
|
|
129
|
+
views: { type: 'integer', minimum: 0 },
|
|
130
|
+
tags: { type: 'array', items: { type: 'string', min_length: 1 } }
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 校验接口请求参数
|
|
136
|
+
* @param {Object} params 请求参数
|
|
137
|
+
* @returns {{code: number, msg: string}} 校验结果
|
|
138
|
+
*/
|
|
139
|
+
function validateRequest(params) {
|
|
140
|
+
const tip = new Validate(ARTICLE_SCHEMA).check(params);
|
|
141
|
+
return tip === '' ? { code: 0, msg: 'ok' } : { code: 1, msg: tip };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(
|
|
145
|
+
validateRequest({
|
|
146
|
+
title: 'mm_validate 使用说明',
|
|
147
|
+
content: '按 JSON Schema 常用子集校验参数。',
|
|
148
|
+
status: 'published'
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
// { code: 0, msg: 'ok' }
|
|
152
|
+
|
|
153
|
+
console.log(validateRequest({ title: 'a', content: '', status: 'deleted' }));
|
|
154
|
+
// { code: 1, msg: 'title: min_length expected >= 2 but got 1' }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 运行示例
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run demo
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
示例覆盖基础类型、对象嵌套、数组元素、接口参数四类场景,源码见 `example/index.js`。
|
|
164
|
+
|
|
165
|
+
## 测试
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npm test
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
使用 Jest 运行单元测试并生成覆盖率报告。`jest.config.js` 已配置语句、分支、函数、行覆盖率均为 **100%**,未达标时命令会失败。
|
|
172
|
+
|
|
173
|
+
## 目录结构
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
mm_validate/
|
|
177
|
+
├── index.js # 入口,导出 Validate
|
|
178
|
+
├── lib/
|
|
179
|
+
│ └── validate.js # 校验器实现
|
|
180
|
+
├── example/
|
|
181
|
+
│ └── index.js # 使用示例
|
|
182
|
+
├── test/
|
|
183
|
+
│ ├── index.test.js
|
|
184
|
+
│ └── validate.test.js
|
|
185
|
+
├── jest.config.js
|
|
186
|
+
└── package.json
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 许可证
|
|
190
|
+
|
|
191
|
+
[ISC](LICENSE)
|
package/example/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Validate } = require('../index');
|
|
4
|
+
|
|
5
|
+
const ARTICLE_SCHEMA = {
|
|
6
|
+
type: 'object',
|
|
7
|
+
required: ['title', 'content', 'status'],
|
|
8
|
+
properties: {
|
|
9
|
+
title: { type: 'string', min_length: 2, max_length: 50 },
|
|
10
|
+
content: { type: 'string', min_length: 1 },
|
|
11
|
+
status: { type: 'string', enum: ['draft', 'published'] },
|
|
12
|
+
views: { type: 'integer', minimum: 0 },
|
|
13
|
+
tags: { type: 'array', items: { type: 'string', min_length: 1 } }
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 输出单次校验结果
|
|
19
|
+
* @param {string} title 场景名称
|
|
20
|
+
* @param {Object} schema 校验配置
|
|
21
|
+
* @param {*} params 调用参数
|
|
22
|
+
* @returns {void}
|
|
23
|
+
*/
|
|
24
|
+
function printResult(title, schema, params) {
|
|
25
|
+
const tip = new Validate(schema).check(params);
|
|
26
|
+
const result = tip === '' ? '通过' : tip;
|
|
27
|
+
console.log(`${title} -> ${result}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 基础类型校验
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
34
|
+
function demoBasic() {
|
|
35
|
+
console.log('=== 基础类型校验 ===');
|
|
36
|
+
printResult('字符串通过', { type: 'string' }, 'hello');
|
|
37
|
+
printResult('字符串失败', { type: 'string' }, 100);
|
|
38
|
+
printResult('整数通过', { type: 'integer', minimum: 0, maximum: 150 }, 30);
|
|
39
|
+
printResult('整数失败', { type: 'integer', minimum: 0 }, 1.5);
|
|
40
|
+
printResult('枚举失败', { type: 'string', enum: ['admin', 'user'] }, 'guest');
|
|
41
|
+
printResult('正则失败', { type: 'string', pattern: '^[a-z]+$' }, 'qww123');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 对象与嵌套字段校验
|
|
46
|
+
* @returns {void}
|
|
47
|
+
*/
|
|
48
|
+
function demoObject() {
|
|
49
|
+
console.log('=== 对象校验 ===');
|
|
50
|
+
const schema = {
|
|
51
|
+
type: 'object',
|
|
52
|
+
required: ['name', 'age'],
|
|
53
|
+
properties: {
|
|
54
|
+
name: { type: 'string', min_length: 1 },
|
|
55
|
+
age: { type: 'integer', minimum: 0, maximum: 150 },
|
|
56
|
+
address: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
required: ['city'],
|
|
59
|
+
properties: { city: { type: 'string' } }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
printResult('对象通过', schema, { name: '张三', age: 28, address: { city: '广州' } });
|
|
65
|
+
printResult('缺少必填字段', schema, { name: '张三' });
|
|
66
|
+
printResult('嵌套字段错误', schema, { name: '张三', age: 28, address: {} });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 数组元素校验
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
function demoArray() {
|
|
74
|
+
console.log('=== 数组校验 ===');
|
|
75
|
+
const schema = { type: 'array', items: { type: 'number' } };
|
|
76
|
+
printResult('数组通过', schema, [1, 2, 3]);
|
|
77
|
+
printResult('数组元素错误', schema, [1, 'x', 3]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 校验接口请求参数并返回统一结构
|
|
82
|
+
* @param {Object} params 请求参数
|
|
83
|
+
* @returns {{code: number, msg: string}} 校验结果
|
|
84
|
+
*/
|
|
85
|
+
function validateRequest(params) {
|
|
86
|
+
const tip = new Validate(ARTICLE_SCHEMA).check(params);
|
|
87
|
+
return tip === '' ? { code: 0, msg: 'ok' } : { code: 1, msg: tip };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 接口参数校验场景
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
94
|
+
function demoRequest() {
|
|
95
|
+
console.log('=== 接口参数校验 ===');
|
|
96
|
+
const ok_params = {
|
|
97
|
+
title: 'mm_validate 使用说明',
|
|
98
|
+
content: '按 JSON Schema 常用子集校验参数。',
|
|
99
|
+
status: 'published',
|
|
100
|
+
views: 100,
|
|
101
|
+
tags: ['nodejs', 'validate']
|
|
102
|
+
};
|
|
103
|
+
console.log('合法参数 ->', validateRequest(ok_params));
|
|
104
|
+
|
|
105
|
+
const bad_params = { title: 'a', content: '', status: 'deleted', views: -1, tags: [''] };
|
|
106
|
+
console.log('非法参数 ->', validateRequest(bad_params));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 运行全部示例
|
|
111
|
+
* @returns {void}
|
|
112
|
+
*/
|
|
113
|
+
function demo() {
|
|
114
|
+
demoBasic();
|
|
115
|
+
demoObject();
|
|
116
|
+
demoArray();
|
|
117
|
+
demoRequest();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
demo();
|
package/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Admin = require('mm_corejs').Admin;
|
|
4
|
+
const Validate = require('./lib/validate');
|
|
5
|
+
|
|
6
|
+
const admin = new Admin();
|
|
7
|
+
admin.setClass(Validate);
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
Validate,
|
|
11
|
+
admin,
|
|
12
|
+
/**
|
|
13
|
+
* 获取指定作用域的校验器实例,不存在时创建
|
|
14
|
+
* @param {string} [scope] 作用域名称,缺省时使用 os
|
|
15
|
+
* @param {Object} [config] 校验配置(JSON Schema 常用子集)
|
|
16
|
+
* @returns {Validate} 校验器实例
|
|
17
|
+
*/
|
|
18
|
+
validateAdmin(scope, config = {}) {
|
|
19
|
+
if (!scope) {
|
|
20
|
+
scope = 'os';
|
|
21
|
+
}
|
|
22
|
+
return admin.get(scope, config);
|
|
23
|
+
}
|
|
24
|
+
};
|
package/lib/validate.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 参数验证器 —— 按 JSON Schema 常用子集校验参数,返回首个错误信息。
|
|
5
|
+
*
|
|
6
|
+
* 支持的关键字:
|
|
7
|
+
* type / enum / const / min_length / max_length / minimum / maximum
|
|
8
|
+
* pattern / required / properties / items
|
|
9
|
+
*
|
|
10
|
+
* 用法:
|
|
11
|
+
* new Validate(schema).check(params) === '' 表示通过。
|
|
12
|
+
*/
|
|
13
|
+
class Validate {
|
|
14
|
+
/**
|
|
15
|
+
* 构造函数
|
|
16
|
+
* @param {Object} config 校验配置(JSON Schema 常用子集),非对象时视为不校验
|
|
17
|
+
*/
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 校验参数
|
|
24
|
+
* @param {*} params 调用参数
|
|
25
|
+
* @returns {string} 验证通过返回空字符串,否则返回首个错误信息
|
|
26
|
+
*/
|
|
27
|
+
check(params) {
|
|
28
|
+
return this._checkSchema(params, this.config, '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------- 校验编排 ----------
|
|
32
|
+
/**
|
|
33
|
+
* 按 schema 递归校验任意值,按关键字顺序短路返回首个错误
|
|
34
|
+
* @param {*} value 待校验值
|
|
35
|
+
* @param {Object} schema 校验配置
|
|
36
|
+
* @param {string} path 字段路径
|
|
37
|
+
* @returns {string} 错误信息或空字符串
|
|
38
|
+
*/
|
|
39
|
+
_checkSchema(value, schema, path) {
|
|
40
|
+
if (!this._isObject(schema)) return '';
|
|
41
|
+
return (
|
|
42
|
+
this._checkType(value, schema, path) ||
|
|
43
|
+
this._checkEnum(value, schema, path) ||
|
|
44
|
+
this._checkConst(value, schema, path) ||
|
|
45
|
+
this._checkLength(value, schema, path) ||
|
|
46
|
+
this._checkRange(value, schema, path) ||
|
|
47
|
+
this._checkPattern(value, schema, path) ||
|
|
48
|
+
this._checkRequired(value, schema, path) ||
|
|
49
|
+
this._checkProperties(value, schema, path) ||
|
|
50
|
+
this._checkItems(value, schema, path)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------- 关键字校验 ----------
|
|
55
|
+
/**
|
|
56
|
+
* 校验 type 关键字
|
|
57
|
+
* @param {*} value 待校验值
|
|
58
|
+
* @param {Object} schema 校验配置
|
|
59
|
+
* @param {string} path 字段路径
|
|
60
|
+
* @returns {string} 错误信息或空字符串
|
|
61
|
+
*/
|
|
62
|
+
_checkType(value, schema, path) {
|
|
63
|
+
if (schema.type === undefined) return '';
|
|
64
|
+
if (this._isType(value, schema.type)) return '';
|
|
65
|
+
const detail = `type expected "${schema.type}" but got "${this._getTypeName(value)}"`;
|
|
66
|
+
return this._getPathMessage(path, detail);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 校验 enum 关键字
|
|
71
|
+
* @param {*} value 待校验值
|
|
72
|
+
* @param {Object} schema 校验配置
|
|
73
|
+
* @param {string} path 字段路径
|
|
74
|
+
* @returns {string} 错误信息或空字符串
|
|
75
|
+
*/
|
|
76
|
+
_checkEnum(value, schema, path) {
|
|
77
|
+
if (!Array.isArray(schema.enum)) return '';
|
|
78
|
+
if (schema.enum.includes(value)) return '';
|
|
79
|
+
const detail = `enum expected ${JSON.stringify(schema.enum)} but got ${JSON.stringify(value)}`;
|
|
80
|
+
return this._getPathMessage(path, detail);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 校验 const 关键字
|
|
85
|
+
* @param {*} value 待校验值
|
|
86
|
+
* @param {Object} schema 校验配置
|
|
87
|
+
* @param {string} path 字段路径
|
|
88
|
+
* @returns {string} 错误信息或空字符串
|
|
89
|
+
*/
|
|
90
|
+
_checkConst(value, schema, path) {
|
|
91
|
+
if (schema.const === undefined) return '';
|
|
92
|
+
if (value === schema.const) return '';
|
|
93
|
+
const detail = `const expected ${JSON.stringify(schema.const)} but got ${JSON.stringify(value)}`;
|
|
94
|
+
return this._getPathMessage(path, detail);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 校验 min_length / max_length 关键字
|
|
99
|
+
* @param {*} value 待校验值
|
|
100
|
+
* @param {Object} schema 校验配置
|
|
101
|
+
* @param {string} path 字段路径
|
|
102
|
+
* @returns {string} 错误信息或空字符串
|
|
103
|
+
*/
|
|
104
|
+
_checkLength(value, schema, path) {
|
|
105
|
+
if (typeof value !== 'string') return '';
|
|
106
|
+
if (typeof schema.min_length === 'number' && value.length < schema.min_length) {
|
|
107
|
+
const detail = `min_length expected >= ${schema.min_length} but got ${value.length}`;
|
|
108
|
+
return this._getPathMessage(path, detail);
|
|
109
|
+
}
|
|
110
|
+
if (typeof schema.max_length === 'number' && value.length > schema.max_length) {
|
|
111
|
+
const detail = `max_length expected <= ${schema.max_length} but got ${value.length}`;
|
|
112
|
+
return this._getPathMessage(path, detail);
|
|
113
|
+
}
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 校验 minimum / maximum 关键字
|
|
119
|
+
* @param {*} value 待校验值
|
|
120
|
+
* @param {Object} schema 校验配置
|
|
121
|
+
* @param {string} path 字段路径
|
|
122
|
+
* @returns {string} 错误信息或空字符串
|
|
123
|
+
*/
|
|
124
|
+
_checkRange(value, schema, path) {
|
|
125
|
+
if (typeof value !== 'number') return '';
|
|
126
|
+
if (typeof schema.minimum === 'number' && value < schema.minimum) {
|
|
127
|
+
const detail = `minimum expected >= ${schema.minimum} but got ${value}`;
|
|
128
|
+
return this._getPathMessage(path, detail);
|
|
129
|
+
}
|
|
130
|
+
if (typeof schema.maximum === 'number' && value > schema.maximum) {
|
|
131
|
+
const detail = `maximum expected <= ${schema.maximum} but got ${value}`;
|
|
132
|
+
return this._getPathMessage(path, detail);
|
|
133
|
+
}
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 校验 pattern 关键字
|
|
139
|
+
* @param {*} value 待校验值
|
|
140
|
+
* @param {Object} schema 校验配置
|
|
141
|
+
* @param {string} path 字段路径
|
|
142
|
+
* @returns {string} 错误信息或空字符串
|
|
143
|
+
*/
|
|
144
|
+
_checkPattern(value, schema, path) {
|
|
145
|
+
if (typeof value !== 'string') return '';
|
|
146
|
+
if (typeof schema.pattern !== 'string') return '';
|
|
147
|
+
if (new RegExp(schema.pattern).test(value)) return '';
|
|
148
|
+
const detail = `pattern expected match /${schema.pattern}/ but got "${value}"`;
|
|
149
|
+
return this._getPathMessage(path, detail);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 校验 required 关键字
|
|
154
|
+
* @param {*} value 待校验值
|
|
155
|
+
* @param {Object} schema 校验配置
|
|
156
|
+
* @param {string} path 字段路径
|
|
157
|
+
* @returns {string} 错误信息或空字符串
|
|
158
|
+
*/
|
|
159
|
+
_checkRequired(value, schema, path) {
|
|
160
|
+
if (!Array.isArray(schema.required)) return '';
|
|
161
|
+
if (!this._isObject(value)) return '';
|
|
162
|
+
for (const key of schema.required) {
|
|
163
|
+
if (this._isOwnProperty(value, key)) continue;
|
|
164
|
+
return this._getPathMessage(this._getKeyPath(path, key), 'required missing');
|
|
165
|
+
}
|
|
166
|
+
return '';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 校验 properties 关键字(递归)
|
|
171
|
+
* @param {*} value 待校验值
|
|
172
|
+
* @param {Object} schema 校验配置
|
|
173
|
+
* @param {string} path 字段路径
|
|
174
|
+
* @returns {string} 错误信息或空字符串
|
|
175
|
+
*/
|
|
176
|
+
_checkProperties(value, schema, path) {
|
|
177
|
+
if (!this._isObject(schema.properties)) return '';
|
|
178
|
+
if (!this._isObject(value)) return '';
|
|
179
|
+
for (const key of Object.keys(schema.properties)) {
|
|
180
|
+
if (!this._isOwnProperty(value, key)) continue;
|
|
181
|
+
const tip = this._checkSchema(value[key], schema.properties[key], this._getKeyPath(path, key));
|
|
182
|
+
if (tip) return tip;
|
|
183
|
+
}
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 校验 items 关键字(递归)
|
|
189
|
+
* @param {*} value 待校验值
|
|
190
|
+
* @param {Object} schema 校验配置
|
|
191
|
+
* @param {string} path 字段路径
|
|
192
|
+
* @returns {string} 错误信息或空字符串
|
|
193
|
+
*/
|
|
194
|
+
_checkItems(value, schema, path) {
|
|
195
|
+
if (!this._isObject(schema.items)) return '';
|
|
196
|
+
if (!Array.isArray(value)) return '';
|
|
197
|
+
for (let i = 0; i < value.length; i++) {
|
|
198
|
+
const tip = this._checkSchema(value[i], schema.items, this._getArrayPath(path, i));
|
|
199
|
+
if (tip) return tip;
|
|
200
|
+
}
|
|
201
|
+
return '';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------- 类型判定 ----------
|
|
205
|
+
/**
|
|
206
|
+
* 判断值是否匹配指定 JSON 类型
|
|
207
|
+
* @param {*} value 待判定值
|
|
208
|
+
* @param {string} type 类型名称
|
|
209
|
+
* @returns {boolean} 是否匹配
|
|
210
|
+
*/
|
|
211
|
+
_isType(value, type) {
|
|
212
|
+
switch (type) {
|
|
213
|
+
case 'null':
|
|
214
|
+
return value === null;
|
|
215
|
+
case 'array':
|
|
216
|
+
return Array.isArray(value);
|
|
217
|
+
case 'object':
|
|
218
|
+
return this._isObject(value);
|
|
219
|
+
case 'string':
|
|
220
|
+
return typeof value === 'string';
|
|
221
|
+
case 'number':
|
|
222
|
+
return this._isFiniteNumber(value);
|
|
223
|
+
case 'integer':
|
|
224
|
+
return Number.isInteger(value);
|
|
225
|
+
case 'boolean':
|
|
226
|
+
return typeof value === 'boolean';
|
|
227
|
+
default:
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 获取值的 JSON 类型名称
|
|
234
|
+
* @param {*} value 待判定值
|
|
235
|
+
* @returns {string} 类型名称
|
|
236
|
+
*/
|
|
237
|
+
_getTypeName(value) {
|
|
238
|
+
if (value === null) return 'null';
|
|
239
|
+
if (Array.isArray(value)) return 'array';
|
|
240
|
+
return typeof value;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 判断值是否为有限数字(排除 NaN / Infinity)
|
|
245
|
+
* @param {*} value 待判定值
|
|
246
|
+
* @returns {boolean} 是否为有限数字
|
|
247
|
+
*/
|
|
248
|
+
_isFiniteNumber(value) {
|
|
249
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 判断值是否为普通对象(排除 null 与数组)
|
|
254
|
+
* @param {*} value 待判定值
|
|
255
|
+
* @returns {boolean} 是否为普通对象
|
|
256
|
+
*/
|
|
257
|
+
_isObject(value) {
|
|
258
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ---------- 路径与属性 ----------
|
|
262
|
+
/**
|
|
263
|
+
* 判断对象自身是否包含指定字段
|
|
264
|
+
* @param {Object} target 目标对象
|
|
265
|
+
* @param {string} key 字段名
|
|
266
|
+
* @returns {boolean} 是否包含该字段
|
|
267
|
+
*/
|
|
268
|
+
_isOwnProperty(target, key) {
|
|
269
|
+
return Object.prototype.hasOwnProperty.call(target, key);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 拼接对象字段路径
|
|
274
|
+
* @param {string} path 父级路径
|
|
275
|
+
* @param {string} key 字段名
|
|
276
|
+
* @returns {string} 字段路径
|
|
277
|
+
*/
|
|
278
|
+
_getKeyPath(path, key) {
|
|
279
|
+
return path ? `${path}.${key}` : key;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 拼接数组元素路径
|
|
284
|
+
* @param {string} path 父级路径
|
|
285
|
+
* @param {number} index 元素下标
|
|
286
|
+
* @returns {string} 元素路径
|
|
287
|
+
*/
|
|
288
|
+
_getArrayPath(path, index) {
|
|
289
|
+
return `${path}[${index}]`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 为错误详情附加路径前缀
|
|
294
|
+
* @param {string} path 字段路径
|
|
295
|
+
* @param {string} detail 错误详情
|
|
296
|
+
* @returns {string} 完整错误信息
|
|
297
|
+
*/
|
|
298
|
+
_getPathMessage(path, detail) {
|
|
299
|
+
return path ? `${path}: ${detail}` : detail;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = Validate;
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_validate",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "这是一个通用的 Node.js 参数验证模块,按 JSON Schema 常用子集校验参数,返回首个错误信息。",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mm_mod",
|
|
7
|
+
"mm_infra",
|
|
8
|
+
"mm_common",
|
|
9
|
+
"超级美眉",
|
|
10
|
+
"参数验证",
|
|
11
|
+
"数据校验",
|
|
12
|
+
"json schema",
|
|
13
|
+
"validate",
|
|
14
|
+
"validator",
|
|
15
|
+
"schema",
|
|
16
|
+
"nodejs",
|
|
17
|
+
"commonjs"
|
|
18
|
+
],
|
|
19
|
+
"layer": "infra",
|
|
20
|
+
"group": "common",
|
|
21
|
+
"author": "邱文武",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"type": "commonjs",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=16"
|
|
26
|
+
},
|
|
27
|
+
"main": "index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"lib",
|
|
31
|
+
"example"
|
|
32
|
+
],
|
|
33
|
+
"directories": {
|
|
34
|
+
"example": "example",
|
|
35
|
+
"lib": "lib"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://gitee.com/qiuwenwu91/mm_validate#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://gitee.com/qiuwenwu91/mm_validate/issues"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://gitee.com/qiuwenwu91/mm_validate.git"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"test": "jest --coverage",
|
|
47
|
+
"demo": "node example/index.js",
|
|
48
|
+
"prepublishOnly": "npm test"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"jest": "^30.5.2"
|
|
52
|
+
}
|
|
53
|
+
}
|