forked from UKSOURCE/cms.hailearning.edu.vn
159 lines
3.7 KiB
JavaScript
159 lines
3.7 KiB
JavaScript
const DEFAULT_LABEL = "Field";
|
|
|
|
const normalizePath = (path) => {
|
|
return String(path || "")
|
|
.replace(/\[(\d+)\]/g, ".$1")
|
|
.replace(/\[\*\]/g, ".*")
|
|
.replace(/\[\]/g, ".*")
|
|
.split(".")
|
|
.filter(Boolean);
|
|
};
|
|
|
|
const toLabel = (path) => {
|
|
if (!path) {
|
|
return DEFAULT_LABEL;
|
|
}
|
|
|
|
return String(path)
|
|
.replace(/\[\d+\]/g, "")
|
|
.replace(/\.\*/g, "")
|
|
.replace(/[._-]+/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim()
|
|
.replace(/^./, (ch) => ch.toUpperCase());
|
|
};
|
|
|
|
const collectMatches = (value, segments, currentPath = [], results = []) => {
|
|
if (segments.length === 0) {
|
|
results.push({
|
|
path: currentPath.join("."),
|
|
value,
|
|
});
|
|
return results;
|
|
}
|
|
|
|
const [segment, ...rest] = segments;
|
|
|
|
if (segment === "*") {
|
|
if (!Array.isArray(value)) {
|
|
return results;
|
|
}
|
|
|
|
value.forEach((item, index) => {
|
|
collectMatches(item, rest, [...currentPath, String(index)], results);
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
if (value === null || value === undefined) {
|
|
return results;
|
|
}
|
|
|
|
if (typeof value !== "object") {
|
|
return results;
|
|
}
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(value, segment)) {
|
|
return results;
|
|
}
|
|
|
|
return collectMatches(value[segment], rest, [...currentPath, segment], results);
|
|
};
|
|
|
|
const countWords = (value) => {
|
|
const normalized = String(value || "")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
|
|
if (!normalized) {
|
|
return 0;
|
|
}
|
|
|
|
return normalized.split(" ").length;
|
|
};
|
|
|
|
const buildErrorMessage = (label, path, maxLength, maxWords) => {
|
|
const target = path ? `${label} (${path})` : label;
|
|
|
|
if (maxLength && maxWords) {
|
|
return `${target} must not exceed ${maxLength} characters or ${maxWords} words.`;
|
|
}
|
|
|
|
if (maxLength) {
|
|
return `${target} must not exceed ${maxLength} characters.`;
|
|
}
|
|
|
|
return `${target} must not exceed ${maxWords} words.`;
|
|
};
|
|
|
|
const validateLengthRules = (payload, rules = []) => {
|
|
const errors = [];
|
|
|
|
for (const rule of rules) {
|
|
const paths = Array.isArray(rule.paths)
|
|
? rule.paths
|
|
: [rule.path].filter(Boolean);
|
|
|
|
for (const path of paths) {
|
|
const segments = normalizePath(path);
|
|
const matches = collectMatches(payload, segments);
|
|
|
|
for (const match of matches) {
|
|
if (typeof match.value !== "string") {
|
|
continue;
|
|
}
|
|
|
|
const normalized = match.value.trim();
|
|
if (!normalized && rule.allowEmpty !== false) {
|
|
continue;
|
|
}
|
|
|
|
const actualLength = normalized.length;
|
|
const actualWords = countWords(normalized);
|
|
const maxLength = Number(rule.maxLength);
|
|
const maxWords = Number(rule.maxWords);
|
|
const exceedsLength = Number.isFinite(maxLength) && maxLength > 0 && actualLength > maxLength;
|
|
const exceedsWords = Number.isFinite(maxWords) && maxWords > 0 && actualWords > maxWords;
|
|
|
|
if (!exceedsLength && !exceedsWords) {
|
|
continue;
|
|
}
|
|
|
|
const label = rule.label || toLabel(path);
|
|
errors.push({
|
|
path: match.path,
|
|
label,
|
|
message: buildErrorMessage(label, match.path, exceedsLength ? maxLength : null, exceedsWords ? maxWords : null),
|
|
maxLength: Number.isFinite(maxLength) ? maxLength : undefined,
|
|
maxWords: Number.isFinite(maxWords) ? maxWords : undefined,
|
|
actualLength,
|
|
actualWords,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
};
|
|
};
|
|
|
|
const summarizeLengthErrors = (validation, limit = 1) => {
|
|
if (!validation || !Array.isArray(validation.errors) || validation.errors.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
return validation.errors
|
|
.slice(0, limit)
|
|
.map((error) => error.message)
|
|
.join(" ");
|
|
};
|
|
|
|
module.exports = {
|
|
validateLengthRules,
|
|
summarizeLengthErrors,
|
|
toLabel,
|
|
};
|