2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
@@ -0,0 +1,15 @@
import type { ExecuteRule } from '../interface';
import { format } from '../util';
const ENUM = 'enum' as const;
const enumerable: ExecuteRule = (rule, value, source, errors, options) => {
rule[ENUM] = Array.isArray(rule[ENUM]) ? rule[ENUM] : [];
if (rule[ENUM].indexOf(value) === -1) {
errors.push(
format(options.messages[ENUM], rule.fullField, rule[ENUM].join(', ')),
);
}
};
export default enumerable;
@@ -0,0 +1,15 @@
import required from './required';
import whitespace from './whitespace';
import type from './type';
import range from './range';
import enumRule from './enum';
import pattern from './pattern';
export default {
required,
whitespace,
type,
range,
enum: enumRule,
pattern,
};
@@ -0,0 +1,37 @@
import type { ExecuteRule } from '../interface';
import { format } from '../util';
const pattern: ExecuteRule = (rule, value, source, errors, options) => {
if (rule.pattern) {
if (rule.pattern instanceof RegExp) {
// if a RegExp instance is passed, reset `lastIndex` in case its `global`
// flag is accidentally set to `true`, which in a validation scenario
// is not necessary and the result might be misleading
rule.pattern.lastIndex = 0;
if (!rule.pattern.test(value)) {
errors.push(
format(
options.messages.pattern.mismatch,
rule.fullField,
value,
rule.pattern,
),
);
}
} else if (typeof rule.pattern === 'string') {
const _pattern = new RegExp(rule.pattern);
if (!_pattern.test(value)) {
errors.push(
format(
options.messages.pattern.mismatch,
rule.fullField,
value,
rule.pattern,
),
);
}
}
}
};
export default pattern;
@@ -0,0 +1,50 @@
import type { ExecuteRule } from '../interface';
import { format } from '../util';
const range: ExecuteRule = (rule, value, source, errors, options) => {
const len = typeof rule.len === 'number';
const min = typeof rule.min === 'number';
const max = typeof rule.max === 'number';
// 正则匹配码点范围从U+010000一直到U+10FFFF的文字(补充平面Supplementary Plane
const spRegexp = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
let val = value;
let key = null;
const num = typeof value === 'number';
const str = typeof value === 'string';
const arr = Array.isArray(value);
if (num) {
key = 'number';
} else if (str) {
key = 'string';
} else if (arr) {
key = 'array';
}
// if the value is not of a supported type for range validation
// the validation rule rule should use the
// type property to also test for a particular type
if (!key) {
return false;
}
if (arr) {
val = value.length;
}
if (str) {
// 处理码点大于U+010000的文字length属性不准确的bug,如"𠮷𠮷𠮷".length !== 3
val = value.replace(spRegexp, '_').length;
}
if (len) {
if (val !== rule.len) {
errors.push(format(options.messages[key].len, rule.fullField, rule.len));
}
} else if (min && !max && val < rule.min) {
errors.push(format(options.messages[key].min, rule.fullField, rule.min));
} else if (max && !min && val > rule.max) {
errors.push(format(options.messages[key].max, rule.fullField, rule.max));
} else if (min && max && (val < rule.min || val > rule.max)) {
errors.push(
format(options.messages[key].range, rule.fullField, rule.min, rule.max),
);
}
};
export default range;
@@ -0,0 +1,14 @@
import type { ExecuteRule } from '../interface';
import { format, isEmptyValue } from '../util';
const required: ExecuteRule = (rule, value, source, errors, options, type) => {
if (
rule.required &&
(!source.hasOwnProperty(rule.field) ||
isEmptyValue(value, type || rule.type))
) {
errors.push(format(options.messages.required, rule.fullField));
}
};
export default required;
+109
View File
@@ -0,0 +1,109 @@
import type { ExecuteRule, Value } from '../interface';
import { format } from '../util';
import required from './required';
import getUrlRegex from './url';
/* eslint max-len:0 */
const pattern = {
// http://emailregex.com/
email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$/,
// url: new RegExp(
// '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$',
// 'i',
// ),
hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i,
};
const types = {
integer(value: Value) {
return types.number(value) && parseInt(value, 10) === value;
},
float(value: Value) {
return types.number(value) && !types.integer(value);
},
array(value: Value) {
return Array.isArray(value);
},
regexp(value: Value) {
if (value instanceof RegExp) {
return true;
}
try {
return !!new RegExp(value);
} catch (e) {
return false;
}
},
date(value: Value) {
return (
typeof value.getTime === 'function' &&
typeof value.getMonth === 'function' &&
typeof value.getYear === 'function' &&
!isNaN(value.getTime())
);
},
number(value: Value) {
if (isNaN(value)) {
return false;
}
return typeof value === 'number';
},
object(value: Value) {
return typeof value === 'object' && !types.array(value);
},
method(value: Value) {
return typeof value === 'function';
},
email(value: Value) {
return (
typeof value === 'string' &&
value.length <= 320 &&
!!value.match(pattern.email)
);
},
url(value: Value) {
return (
typeof value === 'string' &&
value.length <= 2048 &&
!!value.match(getUrlRegex())
);
},
hex(value: Value) {
return typeof value === 'string' && !!value.match(pattern.hex);
},
};
const type: ExecuteRule = (rule, value, source, errors, options) => {
if (rule.required && value === undefined) {
required(rule, value, source, errors, options);
return;
}
const custom = [
'integer',
'float',
'array',
'regexp',
'object',
'method',
'email',
'number',
'date',
'url',
'hex',
];
const ruleType = rule.type;
if (custom.indexOf(ruleType) > -1) {
if (!types[ruleType](value)) {
errors.push(
format(options.messages.types[ruleType], rule.fullField, rule.type),
);
}
// straight typeof check
} else if (ruleType && typeof value !== rule.type) {
errors.push(
format(options.messages.types[ruleType], rule.fullField, rule.type),
);
}
};
export default type;
+72
View File
@@ -0,0 +1,72 @@
// https://github.com/kevva/url-regex/blob/master/index.js
let urlReg: RegExp;
export default () => {
if (urlReg) {
return urlReg;
}
const word = '[a-fA-F\\d:]';
const b = options =>
options && options.includeBoundaries
? `(?:(?<=\\s|^)(?=${word})|(?<=${word})(?=\\s|$))`
: '';
const v4 =
'(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}';
const v6seg = '[a-fA-F\\d]{1,4}';
const v6 = `
(?:
(?:${v6seg}:){7}(?:${v6seg}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8
(?:${v6seg}:){6}(?:${v4}|:${v6seg}|:)| // 1:2:3:4:5:6:: 1:2:3:4:5:6::8 1:2:3:4:5:6::8 1:2:3:4:5:6::1.2.3.4
(?:${v6seg}:){5}(?::${v4}|(?::${v6seg}){1,2}|:)| // 1:2:3:4:5:: 1:2:3:4:5::7:8 1:2:3:4:5::8 1:2:3:4:5::7:1.2.3.4
(?:${v6seg}:){4}(?:(?::${v6seg}){0,1}:${v4}|(?::${v6seg}){1,3}|:)| // 1:2:3:4:: 1:2:3:4::6:7:8 1:2:3:4::8 1:2:3:4::6:7:1.2.3.4
(?:${v6seg}:){3}(?:(?::${v6seg}){0,2}:${v4}|(?::${v6seg}){1,4}|:)| // 1:2:3:: 1:2:3::5:6:7:8 1:2:3::8 1:2:3::5:6:7:1.2.3.4
(?:${v6seg}:){2}(?:(?::${v6seg}){0,3}:${v4}|(?::${v6seg}){1,5}|:)| // 1:2:: 1:2::4:5:6:7:8 1:2::8 1:2::4:5:6:7:1.2.3.4
(?:${v6seg}:){1}(?:(?::${v6seg}){0,4}:${v4}|(?::${v6seg}){1,6}|:)| // 1:: 1::3:4:5:6:7:8 1::8 1::3:4:5:6:7:1.2.3.4
(?::(?:(?::${v6seg}){0,5}:${v4}|(?::${v6seg}){1,7}|:)) // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::1.2.3.4
)(?:%[0-9a-zA-Z]{1,})? // %eth0 %1
`
.replace(/\s*\/\/.*$/gm, '')
.replace(/\n/g, '')
.trim();
// Pre-compile only the exact regexes because adding a global flag make regexes stateful
const v46Exact = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`);
const v4exact = new RegExp(`^${v4}$`);
const v6exact = new RegExp(`^${v6}$`);
const ip = options =>
options && options.exact
? v46Exact
: new RegExp(
`(?:${b(options)}${v4}${b(options)})|(?:${b(options)}${v6}${b(
options,
)})`,
'g',
);
ip.v4 = (options?) =>
options && options.exact
? v4exact
: new RegExp(`${b(options)}${v4}${b(options)}`, 'g');
ip.v6 = (options?) =>
options && options.exact
? v6exact
: new RegExp(`${b(options)}${v6}${b(options)}`, 'g');
const protocol = `(?:(?:[a-z]+:)?//)`;
const auth = '(?:\\S+(?::\\S*)?@)?';
const ipv4 = ip.v4().source;
const ipv6 = ip.v6().source;
const host = '(?:(?:[a-z\\u00a1-\\uffff0-9][-_]*)*[a-z\\u00a1-\\uffff0-9]+)';
const domain =
'(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*';
const tld = `(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))`;
const port = '(?::\\d{2,5})?';
const path = '(?:[/?#][^\\s"]*)?';
const regex = `(?:${protocol}|www\\.)${auth}(?:localhost|${ipv4}|${ipv6}|${host}${domain}${tld})${port}${path}`;
urlReg = new RegExp(`(?:^${regex}$)`, 'i');
return urlReg;
};
@@ -0,0 +1,21 @@
import type { ExecuteRule } from '../interface';
import { format } from '../util';
/**
* Rule for validating whitespace.
*
* @param rule The validation rule.
* @param value The value of the field on the source object.
* @param source The source object being validated.
* @param errors An array of errors that this rule may add
* validation errors to.
* @param options The validation options.
* @param options.messages The validation messages.
*/
const whitespace: ExecuteRule = (rule, value, source, errors, options) => {
if (/^\s+$/.test(value) || value === '') {
errors.push(format(options.messages.whitespace, rule.fullField));
}
};
export default whitespace;